/*
  Copyright (c) 2010-2025, Intel Corporation

  SPDX-License-Identifier: BSD-3-Clause
*/

%locations

/* supress shift-reduces conflict message for dangling else */
/* one for 'if', one for 'cif' */
/* We expect these conflicts and accept the default resolution (shift). It means
that for the example 'if (a) if (b) c; else d;' the else will be associated
with the nearest if. Same happens for 'if (a) cif (b) c; else d;'.  */

/* suppress 5 shift-reduces conflict messages for postfix and funccall expressions */
/* Both postfix_expression and funcall_expression define the same postfix
operators ([, ., ->, ++, --).
For an input like "func().member", the parser encounters ambiguity:
Path 1 (via postfix_expression):
  postfix_expression -> postfix_expression '.' TOKEN_IDENTIFIER
  where postfix_expression = func()

Path 2 (via funcall_expression):
  unary_expression -> funcall_expression -> funcall_expression '.' TOKEN_IDENTIFIER
  where funcall_expression = func()

We can safely suppress these conflicts since both paths produce identical ASTs */

%expect 7

%define parse.error verbose

%code requires {

#define yytnamerr lYYTNameErr


#define YYLTYPE SourcePos

# define YYLLOC_DEFAULT(Current, Rhs, N)                               \
    do                                                                 \
      if (N)                                                           \
        {                                                              \
          (Current).first_line   = YYRHSLOC (Rhs, 1).first_line;       \
          (Current).first_column = YYRHSLOC (Rhs, 1).first_column;     \
          (Current).last_line    = YYRHSLOC (Rhs, N).last_line;        \
          (Current).last_column  = YYRHSLOC (Rhs, N).last_column;      \
          (Current).name         = YYRHSLOC (Rhs, 1).name    ;         \
        }                                                              \
      else                                                             \
        { /* empty RHS */                                              \
          (Current).first_line   = (Current).last_line   =             \
            YYRHSLOC (Rhs, 0).last_line;                               \
          (Current).first_column = (Current).last_column =             \
            YYRHSLOC (Rhs, 0).last_column;                             \
          (Current).name = nullptr;                        /* new */ \
        }                                                              \
    while (0)

struct ForeachDimension;

struct PragmaAttributes {
    enum class AttributeType { none, pragmaloop, pragmawarning };
    PragmaAttributes() {
        aType = AttributeType::none;
        unrollType =  Globals::pragmaUnrollType::none;
        count = -1;
    }
    AttributeType aType;
    Globals::pragmaUnrollType unrollType;
    int count;

    std::string GetString() {
        std::string s;
        if (aType == PragmaAttributes::AttributeType::pragmaloop) {
            s += "loop";
        }
        if (aType == PragmaAttributes::AttributeType::pragmawarning) {
            s += "warning";
        }
        if (unrollType == Globals::pragmaUnrollType::nounroll) {
            s += " nounroll";
        }
        if (unrollType == Globals::pragmaUnrollType::unroll) {
            s += " unroll";
        }
        if (unrollType == Globals::pragmaUnrollType::count) {
            s += " count(" + std::to_string(count) + ")";
        }
        return s;
    }
};

typedef std::pair<Declarator *, TemplateArgs *> SimpleTemplateIDType;

}


%{

#include "decl.h"
#include "expr.h"
#include "func.h"
#include "ispc.h"
#include "module.h"
#include "stmt.h"
#include "sym.h"
#include "type.h"
#include "util.h"

#include <inttypes.h>
#include <llvm/IR/Constants.h>
#include <stdio.h>
#include <variant>

using namespace ispc;

// This macro is defined to be used in printer directives later.
#define SAFE_ACCESS(ptr, expr) ((ptr) ? (ptr)->expr : "nullptr")

#define UNIMPLEMENTED \
        Error(yylloc, "Unimplemented parser functionality %s:%d", \
        __FILE__, __LINE__);

union YYSTYPE;
extern int yylex();

extern char *yytext;

void yyerror(const char *s);

void lCleanUpString(std::string *str);
void lFreeSimpleTemplateID(void *p);
static int lYYTNameErr(char *yyres, const char *yystr);

static void lSuggestBuiltinAlternates();
static void lSuggestParamListAlternates();

static void lAddDeclaration(DeclSpecs *ds, Declarator *decl);
static void lAddTemplateDeclaration(TemplateParms *templateParmList, DeclSpecs *ds, Declarator *decl);
static void lAddTemplateSpecialization(const TemplateArgs &templArgs, DeclSpecs *ds, Declarator *decl);
static void lAddFunctionParams(Declarator *decl);
static void lAddMaskToSymbolTable(SourcePos pos);
static void lAddThreadIndexCountToSymbolTable(SourcePos pos);
static std::string lGetAlternates(std::vector<std::string> &alternates);
static bool lGetConstantIntOrSymbol(Expr *expr, std::variant<std::monostate, int, Symbol*> *value, SourcePos pos, const char *usage);
static StructType *lCreateStructType(const std::string &name, const std::vector<StructDeclaration *> *decl,
                                     AttributeList *attrs, SourcePos pos);

enum class TemplateType { Template, Instantiation, Specialization };
static void lCheckTemplateDeclSpecs(DeclSpecs *ds, SourcePos pos, TemplateType type, const char* name);

static EnumType *lCreateEnumType(const char *name, std::vector<Symbol *> *enums,
                                 SourcePos pos);
static void lFinalizeEnumeratorSymbols(std::vector<Symbol *> &enums,
                                       const EnumType *enumType);

static const char *lBuiltinTokens[] = {
    "assert", "alloca", "bool", "break", "case", "cdo",
    "cfor", "cif", "cwhile", "const", "continue", "default",
    "do", "delete", "double", "else", "enum", "export", "extern", "false",
    "float16", "float", "for", "foreach", "foreach_active", "foreach_tiled",
    "foreach_unique", "goto", "if", "in", "inline",
    "int", "int8", "int16", "int32", "int64", "invoke_sycl", "launch", "new", "NULL",
    "print", "return", "signed", "sizeof", "static", "struct", "switch",
    "sync", "task", "true", "typedef", "uniform", "unmasked", "unsigned",
    "varying", "void", "while", "__attribute__", NULL
};

static const char *lParamListTokens[] = {
    "bool", "const", "double", "enum", "false", "float16", "float", "int",
    "int8", "int16", "int32", "int64", "signed", "struct", "true",
    "uniform", "unsigned", "varying", "void", "__attribute__", NULL
};

struct ForeachDimension {
    ForeachDimension(Symbol *s = nullptr, Expr *b = nullptr, Expr *e = nullptr) {
        sym = s;
        beginExpr = b;
        endExpr = e;
    }
    Symbol *sym;
    Expr *beginExpr, *endExpr;
};

%}

%union {
    uint64_t intVal;
    float  floatVal;
    double doubleVal;
    std::string *stringVal;
    const char *constCharPtr;

    Expr *expr;
    ExprList *exprList;
    const Type *type;
    std::vector<std::pair<const Type *, SourcePos> > *typeList;
    int typeQualifier;
    StorageClass *storageClass;
    Stmt *stmt;
    DeclSpecs *declSpecs;
    AttributeList *attributeList;
    Attribute *attr;
    AttrArgument *attrArg;
    Declaration *declaration;
    std::vector<Declarator *> *declarators;
    std::vector<Declaration *> *declarationList;
    Declarator *declarator;
    std::vector<Declarator *> *structDeclaratorList;
    StructDeclaration *structDeclaration;
    std::vector<StructDeclaration *> *structDeclarationList;
    const EnumType *enumType;
    Symbol *symbol;
    std::vector<Symbol *> *symbolList;
    ForeachDimension *foreachDimension;
    std::vector<ForeachDimension *> *foreachDimensionList;
    std::pair<std::string, SourcePos> *declspecPair;
    std::vector<std::pair<std::string, SourcePos> > *declspecList;
    PragmaAttributes *pragmaAttributes;
    const TemplateArg *templateArg;
    const TemplateArgs *templateArgs;
    const TemplateParam *templateParm;
    TemplateParms *templateParmList;
    const TemplateTypeParmType *templateTypeParm;
    TemplateSymbol *functionTemplateSym;
    SimpleTemplateIDType *simpleTemplateID;
}


%token TOKEN_INT8_CONSTANT TOKEN_UINT8_CONSTANT
%token TOKEN_INT16_CONSTANT TOKEN_UINT16_CONSTANT
%token TOKEN_INT32_CONSTANT TOKEN_UINT32_CONSTANT
%token TOKEN_INT64_CONSTANT TOKEN_UINT64_CONSTANT
%token TOKEN_INT32DOTDOTDOT_CONSTANT TOKEN_UINT32DOTDOTDOT_CONSTANT
%token TOKEN_INT64DOTDOTDOT_CONSTANT TOKEN_UINT64DOTDOTDOT_CONSTANT
%token <stringVal> TOKEN_FLOAT16_CONSTANT
%token TOKEN_FLOAT_CONSTANT TOKEN_DOUBLE_CONSTANT TOKEN_STRING_C_LITERAL TOKEN_STRING_SYCL_LITERAL
%token <stringVal> TOKEN_IDENTIFIER TOKEN_STRING_LITERAL TOKEN_TYPE_NAME
%token TOKEN_PRAGMA TOKEN_NULL
%token <stringVal> TOKEN_TEMPLATE_NAME
%token TOKEN_TEMPLATE TOKEN_TYPENAME
%token TOKEN_PTR_OP TOKEN_INC_OP TOKEN_DEC_OP TOKEN_LEFT_OP TOKEN_RIGHT_OP
%token TOKEN_LE_OP TOKEN_GE_OP TOKEN_EQ_OP TOKEN_NE_OP
%token TOKEN_AND_OP TOKEN_OR_OP TOKEN_MUL_ASSIGN TOKEN_DIV_ASSIGN TOKEN_MOD_ASSIGN
%token TOKEN_ADD_ASSIGN TOKEN_SUB_ASSIGN TOKEN_LEFT_ASSIGN TOKEN_RIGHT_ASSIGN
%token TOKEN_AND_ASSIGN TOKEN_OR_ASSIGN TOKEN_XOR_ASSIGN
%token TOKEN_SIZEOF TOKEN_NEW TOKEN_DELETE TOKEN_IN TOKEN_ALLOCA
%token <stringVal> TOKEN_INTRINSIC_CALL

%token TOKEN_EXTERN TOKEN_EXPORT TOKEN_STATIC TOKEN_INLINE TOKEN_NOINLINE TOKEN_VECTORCALL TOKEN_REGCALL TOKEN_TASK TOKEN_DECLSPEC
%token TOKEN_UNIFORM TOKEN_VARYING TOKEN_TYPEDEF TOKEN_SOA TOKEN_UNMASKED
%token TOKEN_INT TOKEN_SIGNED TOKEN_UNSIGNED TOKEN_FLOAT16 TOKEN_FLOAT TOKEN_DOUBLE
%token TOKEN_INT8 TOKEN_INT16 TOKEN_INT64 TOKEN_CONST TOKEN_VOID TOKEN_BOOL
%token TOKEN_UINT8 TOKEN_UINT16 TOKEN_UINT TOKEN_UINT64
%token TOKEN_ENUM TOKEN_STRUCT TOKEN_TRUE TOKEN_FALSE

%token TOKEN_CASE TOKEN_DEFAULT TOKEN_IF TOKEN_ELSE TOKEN_SWITCH
%token TOKEN_WHILE TOKEN_DO TOKEN_LAUNCH TOKEN_FOREACH TOKEN_FOREACH_TILED
%token TOKEN_FOREACH_UNIQUE TOKEN_FOREACH_ACTIVE TOKEN_DOTDOTDOT
%token TOKEN_FOR TOKEN_GOTO TOKEN_CONTINUE TOKEN_BREAK TOKEN_RETURN
%token TOKEN_CIF TOKEN_CDO TOKEN_CFOR TOKEN_CWHILE
%token TOKEN_SYNC TOKEN_PRINT TOKEN_ASSERT TOKEN_INVOKE_SYCL
%token TOKEN_ATTRIBUTE

%type <expr> primary_expression postfix_expression integer_dotdotdot
%type <expr> unary_expression cast_expression funcall_expression launch_expression intrincall_expression
%type <expr> multiplicative_expression additive_expression shift_expression
%type <expr> relational_expression equality_expression and_expression
%type <expr> exclusive_or_expression inclusive_or_expression
%type <expr> invoke_sycl_expression
%type <expr> logical_and_expression logical_or_expression new_expression
%type <expr> conditional_expression assignment_expression expression
%type <expr> initializer constant_expression for_test
%type <exprList> argument_expression_list initializer_list

%type <stmt> attributed_statement labeled_statement compound_statement for_init_statement statement
%type <stmt> expression_statement selection_statement iteration_statement
%type <stmt> jump_statement statement_list declaration_statement print_statement
%type <stmt> assert_statement sync_statement delete_statement unmasked_statement

%type <declaration> declaration parameter_declaration
%type <declarators> init_declarator_list
%type <declarationList> parameter_list parameter_type_list
%type <declarator> declarator pointer reference
%type <declarator> init_declarator direct_declarator struct_declarator
%type <declarator> abstract_declarator direct_abstract_declarator

%type <structDeclaratorList> struct_declarator_list
%type <structDeclaration> struct_declaration
%type <structDeclarationList> struct_declaration_list

%type <symbolList> enumerator_list
%type <symbol> enumerator foreach_identifier foreach_active_identifier template_int_parameter template_enum_parameter
%type <enumType> enum_specifier

%type <type> specifier_qualifier_list struct_or_union_specifier
%type <type> struct_or_union_and_name
%type <type> type_specifier type_name rate_qualified_type_specifier
%type <type> short_vec_specifier
%type <typeList> type_specifier_list
%type <type> atomic_var_type_specifier int_constant_type template_int_constant_type

%type <typeQualifier> type_qualifier type_qualifier_list
%type <storageClass> storage_class_specifier
%type <declSpecs> declaration_specifiers

%type <attributeList> attribute_list attribute_specifier
%type <attr> attribute
%type <attrArg> attribute_argument

%type <stringVal> string_constant intrinsic_name
%type <constCharPtr> struct_or_union_name enum_identifier goto_identifier
%type <constCharPtr> foreach_unique_identifier

%type <intVal> int_constant soa_width_specifier rate_qualified_new

%type <pragmaAttributes> pragma
%type <foreachDimension> foreach_dimension_specifier
%type <foreachDimensionList> foreach_dimension_list

%type <declspecPair> declspec_item
%type <declspecList> declspec_specifier declspec_list

%type <constCharPtr> template_identifier
%type <templateArg> template_argument
%type <templateArgs> template_argument_list
%type <simpleTemplateID> simple_template_id template_function_specialization_declaration
%type <templateTypeParm> template_type_parameter
%type <templateParm> template_parameter
%type <templateParmList> template_parameter_list template_head
%type <functionTemplateSym> template_declaration

%precedence STRUCT_WITHOUT_ATTRIBUTE
%precedence TOKEN_ATTRIBUTE

%destructor { lCleanUpString($$); } <stringVal>
%destructor { delete $$; } <storageClass>
%destructor { delete $$; } <attributeList>
%destructor { delete $$; } <attr>
%destructor { delete $$; } <attrArg>
// TODO! destructos for all semantic types that return pointer to heap-allocated memory
// e.g., tests/lit-tests/2599.ispc

// Print semantic values for debugging (under --yydebug)
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, c_str())); } <stringVal>
%printer { fprintf(yyo, "%s", $$); } <constCharPtr>
%printer { fprintf(yyo, "%" PRIu64, $$); } <intVal>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetString().c_str())); } <expr>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetString().c_str())); } <exprList>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetString().c_str())); } <stmt>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetString().c_str())); } <declaration>
%printer {
  fprintf(yyo, "<");
  for(auto &i : *$$) {
    fprintf(yyo, "%s:", SAFE_ACCESS(i, GetString().c_str()));
  }
  fprintf(yyo, ">");
} <declarationList>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetString().c_str())); } <declarator>
%printer {
  fprintf(yyo, "<");
  for(auto &i : *$$) {
    fprintf(yyo, "%s,", SAFE_ACCESS(i, GetString().c_str()));
  }
  fprintf(yyo, ">");
} <declarators>
%printer {
  fprintf(yyo, "<");
  for(auto &i : *$$) {
    fprintf(yyo, "%s,", SAFE_ACCESS(i, GetString().c_str()));
  }
  fprintf(yyo, ">");
} <structDeclaratorList>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetString().c_str())); } <structDeclaration>
%printer {
  fprintf(yyo, "<");
  for(auto &i : *$$) {
    fprintf(yyo, "%s,", SAFE_ACCESS(i, GetString().c_str()));
  }
  fprintf(yyo, ">");
} <structDeclarationList>
%printer {
  fprintf(yyo, "<");
  for(auto &i : *$$) {
    fprintf(yyo, "%s,", SAFE_ACCESS(i, name.c_str()));
  }
  fprintf(yyo, ">");
} <symbolList>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, name.c_str())); } <symbol>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetString().c_str())); } <enumType>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetString().c_str())); } <type>
%printer {
  fprintf(yyo, "<");
  for(auto &i : *$$) {
    fprintf(yyo, "%s,", SAFE_ACCESS(i.first, GetString().c_str()));
  }
  fprintf(yyo, ">");
} <typeList>
%printer { fprintf(yyo, "%s", DeclSpecs::GetTypeQualifiersString($$).c_str()); } <typeQualifier>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetString().c_str())); } <storageClass>
%printer { fprintf(yyo, "%s:", SAFE_ACCESS($$, GetString().c_str())); } <declSpecs>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetString().c_str())); } <attributeList>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetString().c_str())); } <attr>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetString().c_str())); } <attrArg>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetString().c_str())); } <pragmaAttributes>
%printer { fprintf(yyo, "%s", $$->sym->name.c_str()); } <foreachDimension>
%printer {
  fprintf(yyo, "<");
  for (auto &i : *$$) {
    if (i && i->sym) {
      fprintf(yyo, "%s,", i->sym->name.c_str());
    } else {
      fprintf(yyo, "nullptr");
    }
  }
  fprintf(yyo, ">");
} <foreachDimensionList>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, first.c_str())); } <declspecPair>
%printer {
  fprintf(yyo, "<");
  for (auto &i : *$$) {
    fprintf(yyo, "%s,", i.first.c_str());
  }
  fprintf(yyo, ">");
} <declspecList>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetString().c_str())); } <templateArg>
%printer {
  fprintf(yyo, "<");
  for (auto &i : *$$) {
    fprintf(yyo, "%s,", i.GetString().c_str());
  }
  fprintf(yyo, ">");
} <templateArgs>
%printer {
  if ($$ && $$->first) {
    fprintf(yyo, "%s: ", $$->first->name.c_str());
  }
  fprintf(yyo, "<");
  if ($$ && $$->second) {
    for (auto &i : *$$->second) {
      fprintf(yyo, "%s,", i.GetString().c_str());
    }
  }
  fprintf(yyo, ">");
} <simpleTemplateID>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetString().c_str())); } <templateTypeParm>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, GetName().c_str())); } <templateParm>
%printer {
  fprintf(yyo, "<");
  if ($$) {
    for (size_t i = 0; i < $$->GetCount(); ++i) {
      fprintf(yyo, "%s,", SAFE_ACCESS((*$$)[i], GetName().c_str()));
    }
  }
  fprintf(yyo, ">");
} <templateParmList>
%printer { fprintf(yyo, "%s", SAFE_ACCESS($$, name.c_str())); } <functionTemplateSym>

%start translation_unit
%%

string_constant
    : TOKEN_STRING_LITERAL
    {
        $$ = new std::string(*$1);
        lCleanUpString($1);
    }
    | string_constant TOKEN_STRING_LITERAL
    {
        std::string *p_str_cst = new std::string();
        p_str_cst->append(*$1);
        p_str_cst->append(*$2);
        $$ = p_str_cst;
        // Allocated in lStringConst
        lCleanUpString($1);
        lCleanUpString($2);
    }
    ;

primary_expression
    : TOKEN_IDENTIFIER {
        const char *name = $1->c_str();
        Symbol *s = m->symbolTable->LookupVariable(name);
        $$ = nullptr;
        if (s)
            $$ = new SymbolExpr(s, @1);
        else {
            std::vector<Symbol *> funs;
            m->symbolTable->LookupFunction(name, &funs);
            if (funs.size() > 0)
                $$ = new FunctionSymbolExpr(name, funs, {}, TemplateArgs(), @1);
        }
        if ($$ == nullptr) {
            std::vector<std::string> alternates =
                m->symbolTable->ClosestVariableOrFunctionMatch(name);
            std::string alts = lGetAlternates(alternates);
            Error(@1, "Undeclared symbol \"%s\".%s", name, alts.c_str());
        }
        lCleanUpString($1);
    }
    | TOKEN_INT8_CONSTANT {
        $$ = new ConstExpr(AtomicType::UniformInt8->GetAsConstType(),
                           (int8_t)yylval.intVal, @1);
    }
    | TOKEN_UINT8_CONSTANT {
        $$ = new ConstExpr(AtomicType::UniformUInt8->GetAsConstType(),
                           (uint8_t)yylval.intVal, @1);
    }
    | TOKEN_INT16_CONSTANT {
        $$ = new ConstExpr(AtomicType::UniformInt16->GetAsConstType(),
                           (int16_t)yylval.intVal, @1);
    }
    | TOKEN_UINT16_CONSTANT {
        $$ = new ConstExpr(AtomicType::UniformUInt16->GetAsConstType(),
                           (uint16_t)yylval.intVal, @1);
    }
    | TOKEN_INT32_CONSTANT {
        $$ = new ConstExpr(AtomicType::UniformInt32->GetAsConstType(),
                           (int32_t)yylval.intVal, @1);
    }
    | TOKEN_UINT32_CONSTANT {
        $$ = new ConstExpr(AtomicType::UniformUInt32->GetAsConstType(),
                           (uint32_t)yylval.intVal, @1);
    }
    | TOKEN_INT64_CONSTANT {
        $$ = new ConstExpr(AtomicType::UniformInt64->GetAsConstType(),
                           (int64_t)yylval.intVal, @1);
    }
    | TOKEN_UINT64_CONSTANT {
        $$ = new ConstExpr(AtomicType::UniformUInt64->GetAsConstType(),
                           (uint64_t)yylval.intVal, @1);
    }
    | TOKEN_FLOAT16_CONSTANT {
         std::string sval = *$1;
         lCleanUpString($1);
         llvm::Type *hType = llvm::Type::getHalfTy(*g->ctx);
         const llvm::fltSemantics &FS = hType->getFltSemantics();
         llvm::APFloat f16(FS, sval);
         $$ = new ConstExpr(AtomicType::UniformFloat16->GetAsConstType(),
                           f16, @1);
    }
    | TOKEN_FLOAT_CONSTANT {
        llvm::APFloat f(yylval.floatVal);
        $$ = new ConstExpr(AtomicType::UniformFloat->GetAsConstType(),
                           f, @1);
    }
    | TOKEN_DOUBLE_CONSTANT {
        llvm::APFloat d(yylval.doubleVal);
        $$ = new ConstExpr(AtomicType::UniformDouble->GetAsConstType(),
                           d, @1);
    }
    | TOKEN_TRUE {
        $$ = new ConstExpr(AtomicType::UniformBool->GetAsConstType(), true, @1);
    }
    | TOKEN_FALSE {
        $$ = new ConstExpr(AtomicType::UniformBool->GetAsConstType(), false, @1);
    }
    | TOKEN_NULL {
        $$ = new NullPointerExpr(@1);
    }
/*    | TOKEN_STRING_LITERAL
       { UNIMPLEMENTED }*/
    | '(' expression ')' { $$ = $2; }
    | '(' error ')' { $$ = nullptr; }
    ;

launch_expression
    : TOKEN_LAUNCH postfix_expression '(' argument_expression_list ')'
      {
          ConstExpr *oneExpr = new ConstExpr(AtomicType::UniformInt32, (int32_t)1, @2);
          Expr *launchCount[3] = {oneExpr, oneExpr, oneExpr};
          $$ = new FunctionCallExpr($2, $4, Union(@2, @5), true, launchCount);
      }
    | TOKEN_LAUNCH postfix_expression '(' ')'
      {
          ConstExpr *oneExpr = new ConstExpr(AtomicType::UniformInt32, (int32_t)1, @2);
          Expr *launchCount[3] = {oneExpr, oneExpr, oneExpr};
          $$ = new FunctionCallExpr($2, new ExprList(Union(@3,@4)), Union(@2, @4), true, launchCount);
       }

    | TOKEN_LAUNCH '[' assignment_expression ']' postfix_expression '(' argument_expression_list ')'
      {
          ConstExpr *oneExpr = new ConstExpr(AtomicType::UniformInt32, (int32_t)1, @5);
          Expr *launchCount[3] = {$3, oneExpr, oneExpr};
          $$ = new FunctionCallExpr($5, $7, Union(@5,@8), true, launchCount);
      }
    | TOKEN_LAUNCH '[' assignment_expression ']' postfix_expression '(' ')'
      {
          ConstExpr *oneExpr = new ConstExpr(AtomicType::UniformInt32, (int32_t)1, @5);
          Expr *launchCount[3] = {$3, oneExpr, oneExpr};
          $$ = new FunctionCallExpr($5, new ExprList(Union(@5,@6)), Union(@5,@7), true, launchCount);
      }

    | TOKEN_LAUNCH '[' assignment_expression ',' assignment_expression ']' postfix_expression '(' argument_expression_list ')'
      {
          ConstExpr *oneExpr = new ConstExpr(AtomicType::UniformInt32, (int32_t)1, @7);
          Expr *launchCount[3] = {$3, $5, oneExpr};
          $$ = new FunctionCallExpr($7, $9, Union(@7,@10), true, launchCount);
      }
    | TOKEN_LAUNCH '[' assignment_expression ',' assignment_expression ']' postfix_expression '(' ')'
      {
          ConstExpr *oneExpr = new ConstExpr(AtomicType::UniformInt32, (int32_t)1, @7);
          Expr *launchCount[3] = {$3, $5, oneExpr};
          $$ = new FunctionCallExpr($7, new ExprList(Union(@7,@8)), Union(@7,@9), true, launchCount);
      }
    | TOKEN_LAUNCH '[' assignment_expression ']' '[' assignment_expression ']' postfix_expression '(' argument_expression_list ')'
      {
          ConstExpr *oneExpr = new ConstExpr(AtomicType::UniformInt32, (int32_t)1, @8);
          Expr *launchCount[3] = {$6, $3, oneExpr};
          $$ = new FunctionCallExpr($8, $10, Union(@8,@11), true, launchCount);
      }
    | TOKEN_LAUNCH '[' assignment_expression ']' '[' assignment_expression ']' postfix_expression '(' ')'
      {
          ConstExpr *oneExpr = new ConstExpr(AtomicType::UniformInt32, (int32_t)1, @8);
          Expr *launchCount[3] = {$6, $3, oneExpr};
          $$ = new FunctionCallExpr($8, new ExprList(Union(@8,@9)), Union(@8,@10), true, launchCount);
      }

    | TOKEN_LAUNCH '[' assignment_expression ',' assignment_expression ',' assignment_expression ']' postfix_expression '(' argument_expression_list ')'
      {
          Expr *launchCount[3] = {$3, $5, $7};
          $$ = new FunctionCallExpr($9, $11, Union(@9,@12), true, launchCount);
      }
    | TOKEN_LAUNCH '[' assignment_expression ',' assignment_expression ',' assignment_expression ']' postfix_expression '(' ')'
      {
          Expr *launchCount[3] = {$3, $5, $7};
          $$ = new FunctionCallExpr($9, new ExprList(Union(@9,@10)), Union(@9,@11), true, launchCount);
      }
    | TOKEN_LAUNCH '[' assignment_expression ']' '[' assignment_expression ']' '[' assignment_expression ']' postfix_expression '(' argument_expression_list ')'
      {
          Expr *launchCount[3] = {$9, $6, $3};
          $$ = new FunctionCallExpr($11, $13, Union(@11,@14), true, launchCount);
      }
    | TOKEN_LAUNCH '[' assignment_expression ']' '[' assignment_expression ']' '[' assignment_expression ']' postfix_expression '(' ')'
      {
          Expr *launchCount[3] = {$9, $6, $3};
          $$ = new FunctionCallExpr($11, new ExprList(Union(@11,@12)), Union(@11,@13), true, launchCount);
      }


    | TOKEN_LAUNCH '<' postfix_expression '(' argument_expression_list ')' '>'
       {
          Error(Union(@2, @7), "\"launch\" expressions no longer take '<' '>' "
                "around function call expression.");
          $$ = nullptr;
       }
    | TOKEN_LAUNCH '<' postfix_expression '(' ')' '>'
       {
          Error(Union(@2, @6), "\"launch\" expressions no longer take '<' '>' "
                "around function call expression.");
          $$ = nullptr;
       }
    | TOKEN_LAUNCH '[' assignment_expression ']' '<' postfix_expression '(' argument_expression_list ')' '>'
       {
          Error(Union(@5, @10), "\"launch\" expressions no longer take '<' '>' "
                "around function call expression.");
          $$ = nullptr;
       }
    | TOKEN_LAUNCH '[' assignment_expression ']' '<' postfix_expression '(' ')' '>'
       {
          Error(Union(@5, @9), "\"launch\" expressions no longer take '<' '>' "
                "around function call expression.");
          $$ = nullptr;
       }
    ;

invoke_sycl_expression
    : TOKEN_INVOKE_SYCL '(' postfix_expression ')'
      {
          $$ = new FunctionCallExpr($3, new ExprList(@4), Union(@1,@4), false, nullptr, true);
      }
    | TOKEN_INVOKE_SYCL '(' postfix_expression ',' argument_expression_list ')'
      {
          $$ = new FunctionCallExpr($3, $5, Union(@1,@6), false, nullptr, true);
      }
    | TOKEN_INVOKE_SYCL '(' error ')'
      { $$ = nullptr; }
    ;

postfix_expression
    : primary_expression
    | postfix_expression '[' expression ']'
      { $$ = new IndexExpr($1, $3, Union(@1,@4)); }
    | postfix_expression '[' error ']'
      { $$ = nullptr; }
    | launch_expression
    | postfix_expression '.' TOKEN_IDENTIFIER
      {
          $$ = MemberExpr::create($1, yytext, Union(@1,@3), @3, false);
          lCleanUpString($3);
      }
    /* When we have postfix_expression inside template definition, we need to allow cases when
       member name equals to template name or template parameter name. */
    | postfix_expression '.' TOKEN_TYPE_NAME
      {
          $$ = MemberExpr::create($1, yytext, Union(@1,@3), @3, false);
          lCleanUpString($3);
      }
    | postfix_expression '.' TOKEN_TEMPLATE_NAME
      {
          $$ = MemberExpr::create($1, yytext, Union(@1,@3), @3, false);
          lCleanUpString($3);
      }
    | postfix_expression TOKEN_PTR_OP TOKEN_IDENTIFIER
      {
          $$ = MemberExpr::create($1, yytext, Union(@1,@3), @3, true);
          lCleanUpString($3);
      }
    /* When we have postfix_expression inside template definition, we need to allow cases when
       member name equals to template name or template parameter name. */
    | postfix_expression TOKEN_PTR_OP TOKEN_TYPE_NAME
      {
          $$ = MemberExpr::create($1, yytext, Union(@1,@3), @3, true);
          lCleanUpString($3);
      }
    | postfix_expression TOKEN_PTR_OP TOKEN_TEMPLATE_NAME
      {
          $$ = MemberExpr::create($1, yytext, Union(@1,@3), @3, true);
          lCleanUpString($3);
      }
    | postfix_expression TOKEN_INC_OP
      { $$ = new UnaryExpr(UnaryExpr::PostInc, $1, Union(@1,@2)); }
    | postfix_expression TOKEN_DEC_OP
      { $$ = new UnaryExpr(UnaryExpr::PostDec, $1, Union(@1,@2)); }
    ;

intrinsic_name
    : TOKEN_INTRINSIC_CALL
      {
          $$ = $1;
      }
    ;

intrincall_expression
    : intrinsic_name '(' ')'
      {
          std::string *name = $1;
          name->erase(0, 1);
          Symbol* sym = m->AddLLVMIntrinsicDecl(*name, nullptr, Union(@1,@3));
          const char *fname = name->c_str();
          const std::vector<Symbol *> funcs{sym};
          FunctionSymbolExpr *fSym = nullptr;
          if (sym != nullptr)
              fSym = new FunctionSymbolExpr(fname, funcs, {}, TemplateArgs(), @1);
          $$ = new FunctionCallExpr(fSym, new ExprList(Union(@1,@2)), Union(@1,@3));
          delete name;
      }
    | intrinsic_name '(' argument_expression_list ')'
      {
          std::string *name = $1;
          name->erase(0, 1);
          Symbol* sym = m->AddLLVMIntrinsicDecl(*name, $3, Union(@1,@4));
          const char *fname = name->c_str();
          const std::vector<Symbol *> funcs{sym};
          FunctionSymbolExpr *fSym = nullptr;
          if (sym != nullptr)
              fSym = new FunctionSymbolExpr(fname, funcs, {}, TemplateArgs(), @1);
          $$ = new FunctionCallExpr(fSym, $3, Union(@1,@4));
          delete name;
      }
    ;

funcall_expression
    : postfix_expression
    | postfix_expression '(' ')'
      { $$ = new FunctionCallExpr($1, new ExprList(Union(@1,@2)), Union(@1,@3)); }
    | postfix_expression '(' argument_expression_list ')'
      { $$ = new FunctionCallExpr($1, $3, Union(@1,@4)); }
    | postfix_expression '(' error ')'
      { $$ = nullptr; }
    | simple_template_id '(' ')'
      {
          // Create FunctionSymbolExpr with a candidate functions list
          Expr *functionSymbolExpr = nullptr;
          const std::string name = $1->first->name;
          std::vector<Symbol *> funcs;
          m->symbolTable->LookupFunction(name.c_str(), &funcs);
          std::vector<TemplateSymbol *> funcTempls;
          m->symbolTable->LookupFunctionTemplate(name, &funcTempls);
          if (funcs.size() > 0 || funcTempls.size() > 0) {
              TemplateArgs *templArgs = $1->second;
              Assert(templArgs);
              functionSymbolExpr = new FunctionSymbolExpr(name.c_str(), funcs, funcTempls, *templArgs, @1);
              $$ = new FunctionCallExpr(functionSymbolExpr, new ExprList(Union(@1,@2)), Union(@1,@3));
          } else {
              Error(@1, "No matching functions were declared.");
              $$ = nullptr;
          }

          // deallocate SimpleTemplateIDType returned by simple_template_id
          lFreeSimpleTemplateID($1);
      }
    | simple_template_id '(' argument_expression_list ')'
      {
          // Create FunctionSymbolExpr with a candidate functions list
          Expr *functionSymbolExpr = nullptr;
          const std::string name = $1->first->name;
          std::vector<Symbol *> funcs;
          m->symbolTable->LookupFunction(name.c_str(), &funcs);
          std::vector<TemplateSymbol *> funcTempls;
          m->symbolTable->LookupFunctionTemplate(name, &funcTempls);
          if (funcs.size() > 0 || funcTempls.size() > 0) {
              TemplateArgs *templArgs = $1->second;
              Assert(templArgs);
              functionSymbolExpr = new FunctionSymbolExpr(name.c_str(), funcs, funcTempls, *templArgs, @1);
              $$ = new FunctionCallExpr(functionSymbolExpr, $3, Union(@1,@4));
          } else {
              Error(@1, "No matching functions were declared.");
              $$ = nullptr;
          }

          // deallocate SimpleTemplateIDType returned by simple_template_id
          lFreeSimpleTemplateID($1);
      }
    | simple_template_id '(' error ')'
      {
          $$ = nullptr;

          // deallocate SimpleTemplateIDType returned by simple_template_id
          lFreeSimpleTemplateID($1);
      }
    | funcall_expression '[' expression ']'
      { $$ = new IndexExpr($1, $3, Union(@1,@4)); }
    | funcall_expression '.' TOKEN_IDENTIFIER
      {
          $$ = MemberExpr::create($1, yytext, Union(@1,@3), @3, false);
          lCleanUpString($3);
      }
    | funcall_expression '.' TOKEN_TYPE_NAME
      {
          $$ = MemberExpr::create($1, yytext, Union(@1,@3), @3, false);
          lCleanUpString($3);
      }
    | funcall_expression '.' TOKEN_TEMPLATE_NAME
      {
          $$ = MemberExpr::create($1, yytext, Union(@1,@3), @3, false);
          lCleanUpString($3);
      }
    | funcall_expression TOKEN_PTR_OP TOKEN_IDENTIFIER
      {
          $$ = MemberExpr::create($1, yytext, Union(@1,@3), @3, true);
          lCleanUpString($3);
      }
    | funcall_expression TOKEN_PTR_OP TOKEN_TYPE_NAME
      {
          $$ = MemberExpr::create($1, yytext, Union(@1,@3), @3, true);
          lCleanUpString($3);
      }
    | funcall_expression TOKEN_PTR_OP TOKEN_TEMPLATE_NAME
      {
          $$ = MemberExpr::create($1, yytext, Union(@1,@3), @3, true);
          lCleanUpString($3);
      }
    | funcall_expression TOKEN_INC_OP
      { $$ = new UnaryExpr(UnaryExpr::PostInc, $1, Union(@1,@2)); }
    | funcall_expression TOKEN_DEC_OP
      { $$ = new UnaryExpr(UnaryExpr::PostDec, $1, Union(@1,@2)); }
    ;

argument_expression_list
    : assignment_expression      { $$ = new ExprList($1, @1); }
    | argument_expression_list ',' assignment_expression
      {
          ExprList *argList = llvm::dyn_cast<ExprList>($1);
          if (argList == nullptr) {
              AssertPos(@1, m->errorCount > 0);
              argList = new ExprList(@3);
          }
          argList->exprs.push_back($3);
          argList->pos = Union(argList->pos, @3);
          $$ = argList;
      }
    ;

unary_expression
    : funcall_expression
    | intrincall_expression
    | invoke_sycl_expression
    | TOKEN_INC_OP unary_expression
      { $$ = new UnaryExpr(UnaryExpr::PreInc, $2, Union(@1, @2)); }
    | TOKEN_DEC_OP unary_expression
      { $$ = new UnaryExpr(UnaryExpr::PreDec, $2, Union(@1, @2)); }
    | '&' unary_expression
      { $$ = new AddressOfExpr($2, Union(@1, @2)); }
    | '*' unary_expression
      { $$ = new PtrDerefExpr($2, Union(@1, @2)); }
    | '+' cast_expression
      { $$ = $2; }
    | '-' cast_expression
      { $$ = new UnaryExpr(UnaryExpr::Negate, $2, Union(@1, @2)); }
    | '~' cast_expression
      { $$ = new UnaryExpr(UnaryExpr::BitNot, $2, Union(@1, @2)); }
    | '!' cast_expression
      { $$ = new UnaryExpr(UnaryExpr::LogicalNot, $2, Union(@1, @2)); }
    | TOKEN_SIZEOF unary_expression
      { $$ = new SizeOfExpr($2, Union(@1, @2)); }
    | TOKEN_SIZEOF '(' type_name ')'
      { $$ = new SizeOfExpr($3, Union(@1, @4)); }
    | TOKEN_ALLOCA '(' assignment_expression ')'
      {
          $$ = new AllocaExpr($3, Union(@1, @4));
      }
    ;

cast_expression
    : unary_expression
    | '(' type_name ')' cast_expression
      {
          $$ = new TypeCastExpr($2, $4, Union(@1,@4));
      }
    ;

multiplicative_expression
    : cast_expression
    | multiplicative_expression '*' cast_expression
      { $$ = new BinaryExpr(BinaryExpr::Mul, $1, $3, Union(@1, @3)); }
    | multiplicative_expression '/' cast_expression
      { $$ = new BinaryExpr(BinaryExpr::Div, $1, $3, Union(@1, @3)); }
    | multiplicative_expression '%' cast_expression
      { $$ = new BinaryExpr(BinaryExpr::Mod, $1, $3, Union(@1, @3)); }
    ;

additive_expression
    : multiplicative_expression
    | additive_expression '+' multiplicative_expression
      { $$ = new BinaryExpr(BinaryExpr::Add, $1, $3, Union(@1, @3)); }
    | additive_expression '-' multiplicative_expression
      { $$ = new BinaryExpr(BinaryExpr::Sub, $1, $3, Union(@1, @3)); }
    ;

shift_expression
    : additive_expression
    | shift_expression TOKEN_LEFT_OP additive_expression
      { $$ = new BinaryExpr(BinaryExpr::Shl, $1, $3, Union(@1, @3)); }
    | shift_expression TOKEN_RIGHT_OP additive_expression
      { $$ = new BinaryExpr(BinaryExpr::Shr, $1, $3, Union(@1, @3)); }
    ;

relational_expression
    : shift_expression
    | relational_expression '<' shift_expression
      { $$ = new BinaryExpr(BinaryExpr::Lt, $1, $3, Union(@1, @3)); }
    | relational_expression '>' shift_expression
      { $$ = new BinaryExpr(BinaryExpr::Gt, $1, $3, Union(@1, @3)); }
    | relational_expression TOKEN_LE_OP shift_expression
      { $$ = new BinaryExpr(BinaryExpr::Le, $1, $3, Union(@1, @3)); }
    | relational_expression TOKEN_GE_OP shift_expression
      { $$ = new BinaryExpr(BinaryExpr::Ge, $1, $3, Union(@1, @3)); }
    ;

equality_expression
    : relational_expression
    | equality_expression TOKEN_EQ_OP relational_expression
      { $$ = new BinaryExpr(BinaryExpr::Equal, $1, $3, Union(@1,@3)); }
    | equality_expression TOKEN_NE_OP relational_expression
      { $$ = new BinaryExpr(BinaryExpr::NotEqual, $1, $3, Union(@1,@3)); }
    ;

and_expression
    : equality_expression
    | and_expression '&' equality_expression
      { $$ = new BinaryExpr(BinaryExpr::BitAnd, $1, $3, Union(@1, @3)); }
    ;

exclusive_or_expression
    : and_expression
    | exclusive_or_expression '^' and_expression
      { $$ = new BinaryExpr(BinaryExpr::BitXor, $1, $3, Union(@1, @3)); }
    ;

inclusive_or_expression
    : exclusive_or_expression
    | inclusive_or_expression '|' exclusive_or_expression
      { $$ = new BinaryExpr(BinaryExpr::BitOr, $1, $3, Union(@1, @3)); }
    ;

logical_and_expression
    : inclusive_or_expression
    | logical_and_expression TOKEN_AND_OP inclusive_or_expression
      { $$ = new BinaryExpr(BinaryExpr::LogicalAnd, $1, $3, Union(@1, @3)); }
    ;

logical_or_expression
    : logical_and_expression
    | logical_or_expression TOKEN_OR_OP logical_and_expression
      { $$ = new BinaryExpr(BinaryExpr::LogicalOr, $1, $3, Union(@1, @3)); }
    ;

conditional_expression
    : logical_or_expression
    | logical_or_expression '?' expression ':' conditional_expression
      { $$ = new SelectExpr($1, $3, $5, Union(@1,@5)); }
    ;

rate_qualified_new
    : TOKEN_NEW { $$ = 0; }
    | TOKEN_UNIFORM TOKEN_NEW { $$ = TYPEQUAL_UNIFORM; }
    | TOKEN_VARYING TOKEN_NEW { $$ = TYPEQUAL_VARYING; }
    ;

rate_qualified_type_specifier
    : specifier_qualifier_list
    {
        const VectorType *vt = CastType<VectorType>($1);
        if (vt != nullptr) {
            Error(@1, "\"%s\" vector type not supported here yet.",
                  $1->GetString().c_str());
            $$ = nullptr;
        }
        else {
            $$ = $1;
        }
    }
    | soa_width_specifier specifier_qualifier_list
    {
        if ($2 == nullptr)
            $$ = nullptr;
        else {
            int soaWidth = (int)$1;
            const StructType *st = CastType<StructType>($2);
            if (st == nullptr) {
                Error(@1, "\"soa\" qualifier is illegal with non-struct type \"%s\".",
                      $2->GetString().c_str());
                $$ = nullptr;
            }
            else if (soaWidth <= 0 || (soaWidth & (soaWidth - 1)) != 0) {
                Error(@1, "soa<%d> width illegal. Value must be positive power "
                      "of two.", soaWidth);
                $$ = nullptr;
            }
            else
                $$ = st->GetAsSOAType(soaWidth);
        }
    }
    ;

new_expression
    : conditional_expression
    | rate_qualified_new rate_qualified_type_specifier
    {
        $$ = new NewExpr((int32_t)$1, $2, nullptr, nullptr, @1, Union(@1, @2));
    }
    | rate_qualified_new rate_qualified_type_specifier '(' initializer_list ')'
    {
        $$ = new NewExpr((int32_t)$1, $2, $4, nullptr, @1, Union(@1, @2));
    }
    | rate_qualified_new rate_qualified_type_specifier '[' expression ']'
    {
        $$ = new NewExpr((int32_t)$1, $2, nullptr, $4, @1, Union(@1, @4));
    }
    ;

assignment_expression
    : new_expression
    | unary_expression '=' assignment_expression
      { $$ = new AssignExpr(AssignExpr::Assign, $1, $3, Union(@1, @3)); }
    | unary_expression TOKEN_MUL_ASSIGN assignment_expression
      { $$ = new AssignExpr(AssignExpr::MulAssign, $1, $3, Union(@1, @3)); }
    | unary_expression TOKEN_DIV_ASSIGN assignment_expression
      { $$ = new AssignExpr(AssignExpr::DivAssign, $1, $3, Union(@1, @3)); }
    | unary_expression TOKEN_MOD_ASSIGN assignment_expression
      { $$ = new AssignExpr(AssignExpr::ModAssign, $1, $3, Union(@1, @3)); }
    | unary_expression TOKEN_ADD_ASSIGN assignment_expression
      { $$ = new AssignExpr(AssignExpr::AddAssign, $1, $3, Union(@1, @3)); }
    | unary_expression TOKEN_SUB_ASSIGN assignment_expression
      { $$ = new AssignExpr(AssignExpr::SubAssign, $1, $3, Union(@1, @3)); }
    | unary_expression TOKEN_LEFT_ASSIGN assignment_expression
      { $$ = new AssignExpr(AssignExpr::ShlAssign, $1, $3, Union(@1, @3)); }
    | unary_expression TOKEN_RIGHT_ASSIGN assignment_expression
      { $$ = new AssignExpr(AssignExpr::ShrAssign, $1, $3, Union(@1, @3)); }
    | unary_expression TOKEN_AND_ASSIGN assignment_expression
      { $$ = new AssignExpr(AssignExpr::AndAssign, $1, $3, Union(@1, @3)); }
    | unary_expression TOKEN_XOR_ASSIGN assignment_expression
      { $$ = new AssignExpr(AssignExpr::XorAssign, $1, $3, Union(@1, @3)); }
    | unary_expression TOKEN_OR_ASSIGN assignment_expression
      { $$ = new AssignExpr(AssignExpr::OrAssign, $1, $3, Union(@1, @3)); }
    | template_identifier {
        // It looks like the proper place is under primary_expression, but
        // there are reduce/reduce conflicts there. So, we handle it here.
        const std::string name = $1;
        std::vector<Symbol *> funcs;
        m->symbolTable->LookupFunction(name.c_str(), &funcs);
        std::vector<TemplateSymbol *> funcTempls;
        m->symbolTable->LookupFunctionTemplate(name, &funcTempls);
        if (funcs.size() > 0 || funcTempls.size() > 0) {
            $$ = new FunctionSymbolExpr(name.c_str(), funcs, funcTempls, TemplateArgs(), @1);
        } else {
            Error(@1, "No matching functions were declared.");
            $$ = nullptr;
        }

        // deallocate template identifier str
        free((void*)$1);
      }
    ;

expression
    : assignment_expression
    | expression ',' assignment_expression
      { $$ = new BinaryExpr(BinaryExpr::Comma, $1, $3, Union(@1, @3)); }
    ;

constant_expression
    : conditional_expression
    ;

declaration_statement
    : declaration
    {
        if ($1 == nullptr) {
            AssertPos(@1, m->errorCount > 0);
            $$ = nullptr;
        }
        else if ($1->declSpecs->storageClass.IsTypedef()) {
            for (unsigned int i = 0; i < $1->declarators.size(); ++i) {
                if ($1->declarators[i] == nullptr)
                    AssertPos(@1, m->errorCount > 0);
                else
                    m->AddTypeDef($1->declarators[i]->name,
                                  $1->declarators[i]->type,
                                  $1->declarators[i]->pos);
            }
            $$ = nullptr;
        }
        else {
            $1->DeclareFunctions();
            std::vector<VariableDeclaration> vars = $1->GetVariableDeclarations();
            $$ = new DeclStmt(vars, @1);
        }
    }
    ;

declaration
    : declaration_specifiers ';'
      {
          DeclSpecs *ds = $1;
          if (ds && ds->attributeList && ds->attributeList->HasAttribute("aligned")) {

              std::string name;
              const Type *t = ds->baseType;
              if (CastType<StructType>(t)) {
                  name = CastType<StructType>(t)->GetStructName();
              } else if (CastType<UndefinedStructType>(t)) {
                  name = CastType<UndefinedStructType>(t)->GetStructName();
              }

              if (!name.empty()) {
                  Warning(@1, "attribute ignored in declaration of 'struct %s'. It must follow the 'struct' keyword.",
                          name.c_str());
              }
          }
          $$ = new Declaration($1);
      }
    | declaration_specifiers init_declarator_list ';'
      {
          $$ = new Declaration($1, $2);
          // init_declarator_list returns vector of declarators, its copy is
          // saved in Declaration constructor, so it is not needed anymore.
          delete $2;
      }
    ;

soa_width_specifier
    : TOKEN_SOA '<' int_constant '>'
      { $$ = $3; }
    ;

declspec_item
    : TOKEN_IDENTIFIER
    {
        std::pair<std::string, SourcePos> *p = new std::pair<std::string, SourcePos>;
        p->first = *$1;
        p->second = @1;
        $$ = p;
        lCleanUpString($1);
    }
    ;

declspec_list
    : declspec_item
    {
        $$ = new std::vector<std::pair<std::string, SourcePos> >;
        $$->push_back(*$1);
        // declspec_item returns pair that was copied so it is not needed anymore.
        delete $1;
    }
    | declspec_list ',' declspec_item
    {
        if ($1 != nullptr) {
            $1->push_back(*$3);
            // declspec_item returns pair that was copied so it is not needed anymore.
            delete $3;
        }
        $$ = $1;
    }
    ;

declspec_specifier
    : TOKEN_DECLSPEC '(' declspec_list ')'
    {
        // declspec_list returns heap allocated vector that passed up here.
        $$ = $3;
    }
    ;

declaration_specifiers
    : storage_class_specifier
      {
          $$ = new DeclSpecs(nullptr, *$1);
          delete $1;
      }
    | storage_class_specifier declaration_specifiers
      {
          DeclSpecs *ds = (DeclSpecs *)$2;
          if (ds != nullptr) {
              if (!ds->storageClass.IsNone())
                  Error(@1, "Multiple storage class specifiers in a declaration are illegal. "
                        "(Have provided both \"%s\" and \"%s\".)",
                        ds->storageClass.GetString().c_str(),
                        $1->GetString().c_str());
              else
                  ds->storageClass = *$1;
          }
          $$ = ds;
          delete $1;
      }
    | declspec_specifier
      {
          $$ = new DeclSpecs;
          if ($1 != nullptr) {
              $$->declSpecList = *$1;
              // declspec_specifier returns a vector that was copied and it is not needed anymore.
              delete $1;
          }
      }
    | declspec_specifier declaration_specifiers
      {
          DeclSpecs *ds = (DeclSpecs *)$2;
          std::vector<std::pair<std::string, SourcePos> > *declSpecList = $1;
          if (ds != nullptr && declSpecList != nullptr) {
              for (int i = 0; i < (int)declSpecList->size(); ++i)
                  ds->declSpecList.push_back((*declSpecList)[i]);

              // declspec_specifier returns a vector that was copied and it is not needed anymore.
              delete declSpecList;
          }
          $$ = ds;
      }
    | soa_width_specifier
      {
          DeclSpecs *ds = new DeclSpecs;
          ds->soaWidth = (int32_t)$1;
          $$ = ds;
      }
    | soa_width_specifier declaration_specifiers
      {
          DeclSpecs *ds = (DeclSpecs *)$2;
          if (ds != nullptr) {
              if (ds->soaWidth != 0)
                  Error(@1, "soa<> qualifier supplied multiple times in declaration.");
              else
                  ds->soaWidth = (int32_t)$1;
          }
          $$ = ds;
      }
    | type_specifier
      {
          $$ = new DeclSpecs($1);
      }
    | type_specifier '<' int_constant '>'
    {
          DeclSpecs *ds = new DeclSpecs($1);
          ds->vectorSize = (int32_t)$3;
          $$ = ds;
    }
    | type_specifier '<' TOKEN_IDENTIFIER '>'
    {
          DeclSpecs *ds = new DeclSpecs($1);
          const char *name = $3->c_str();
          Symbol *s = m->symbolTable->LookupVariable(name);
          if (s) {
            ds->vectorSize = s;
          } else {
            Error(@3, "Unknown identifier \"%s\" is used to specify the size of a vector type.", name);
          }
          $$ = ds;
          lCleanUpString($3);
    }
    | type_specifier declaration_specifiers
      {
          DeclSpecs *ds = (DeclSpecs *)$2;
          if (ds != nullptr) {
              if (ds->baseType != nullptr) {
                  if( ds->baseType->IsUnsignedType()) {
                      Error(@1, "Redefining uint8/uint16/uint32/uint64 type "
                      "which is part of ISPC language since version 1.13. "
                      "Remove this typedef or use ISPC_UINT_IS_DEFINED to "
                      "detect that these types are defined.");
                  } else if (CastType<StructType>(ds->baseType)) {
                      // Skip the error if the base type is a struct type to
                      // support typedef struct foo { } foo;
                      ;
                  } else
                      Error(@1, "Multiple types provided for declaration.");
              }
              ds->baseType = $1;
          }
          $$ = ds;
      }
    | type_qualifier
      {
          $$ = new DeclSpecs(nullptr, StorageClass::NONE, $1);
      }
    | type_qualifier declaration_specifiers
      {
          DeclSpecs *ds = (DeclSpecs *)$2;
          if (ds != nullptr)
              ds->typeQualifiers |= $1;
          $$ = ds;
      }
    | attribute_specifier
      {
          AttributeList *al = $1;
          DeclSpecs *ds = new DeclSpecs();
          if (al) {
              ds->AddAttrList(*al);
              delete al;
          }
          $$ = ds;
      }
    | attribute_specifier declaration_specifiers
      {
          DeclSpecs *ds = (DeclSpecs *)$2;
          AttributeList *al = $1;
          if (ds && al) {
              ds->AddAttrList(*al);
              delete al;
          }
          $$ = ds;
      }
    ;

attribute_specifier
    : TOKEN_ATTRIBUTE '(' '(' attribute_list ')' ')'
      {
          $$ = $4;
      }
    ;

attribute_list
    : attribute
      {
          Attribute *attr = $1;
          AttributeList *al = new AttributeList;
          al->AddAttribute(*attr);
          $$ = al;
          delete attr;
      }
    | attribute_list ',' attribute
      {
          Attribute *attr = $3;
          AttributeList *al = $1;
          al->AddAttribute(*attr);
          $$ = al;
          delete attr;
      }
    ;

attribute
    : TOKEN_IDENTIFIER
      {
          std::string *str = $1;;
          $$ = new Attribute(*str);
          // cleanup TOKEN_IDENTIFIER string
          lCleanUpString(str);
      }
    | TOKEN_IDENTIFIER '(' attribute_argument ')'
      {
          std::string *str = $1;;
          AttrArgument *arg = $3;
          $$ = new Attribute(*str, *arg);
          delete arg;
          // cleanup TOKEN_IDENTIFIER string
          lCleanUpString(str);
      }
    ;

attribute_argument
    : int_constant
      {
          $$ = new AttrArgument($1);
      }
    | string_constant
      {
          std::string *str = $1;;
          $$ = new AttrArgument(*str);
          // deallocate std::string of string_constant
          lCleanUpString(str);
      }
    ;

init_declarator_list
    : init_declarator
      {
          std::vector<Declarator *> *dl = new std::vector<Declarator *>;
          if ($1 != nullptr)
              dl->push_back($1);
          $$ = dl;
      }
    | init_declarator_list ',' init_declarator
      {
          std::vector<Declarator *> *dl = (std::vector<Declarator *> *)$1;
          if (dl == nullptr) {
              AssertPos(@1, m->errorCount > 0);
              dl = new std::vector<Declarator *>;
          }
          if ($3 != nullptr)
              dl->push_back($3);
          $$ = dl;
      }
    ;

init_declarator
    : declarator
    | declarator '=' initializer
      {
          if ($1 != nullptr)
              $1->initExpr = $3;
          $$ = $1;
      }
    ;

storage_class_specifier
    : TOKEN_TYPEDEF { $$ = new StorageClass(StorageClass::TYPEDEF); }
    | TOKEN_EXTERN { $$ = new StorageClass(StorageClass::EXT); }
    | TOKEN_EXTERN TOKEN_STRING_C_LITERAL  { $$ = new StorageClass(StorageClass::EXT_C); }
    | TOKEN_EXTERN TOKEN_STRING_SYCL_LITERAL  { $$ = new StorageClass(StorageClass::EXT_SYCL); }
    | TOKEN_STATIC { $$ = new StorageClass(StorageClass::STATIC); }
    ;

type_specifier
    : atomic_var_type_specifier { $$ = $1; }
    | TOKEN_TYPE_NAME
    {
        const Type *t = m->symbolTable->LookupType(yytext);
        $$ = t;
        lCleanUpString($1);
    }
    | struct_or_union_specifier { $$ = $1; }
    | enum_specifier { $$ = $1; }
    ;

type_specifier_list
    : type_specifier
    {
        if ($1 == nullptr)
            $$ = nullptr;
        else {
            std::vector<std::pair<const Type *, SourcePos> > *vec =
                new std::vector<std::pair<const Type *, SourcePos> >;
            vec->push_back(std::make_pair($1, @1));
            $$ = vec;
        }
    }
    | type_specifier_list ',' type_specifier
    {
        $$ = $1;
        if ($1 == nullptr)
            Assert(m->errorCount > 0);
        else
            $$->push_back(std::make_pair($3, @3));
    }
    ;

atomic_var_type_specifier
    : TOKEN_VOID { $$ = AtomicType::Void; }
    | TOKEN_BOOL    { $$ = AtomicType::UniformBool->GetAsUnboundVariabilityType(); }
    | TOKEN_INT8    { $$ = AtomicType::UniformInt8->GetAsUnboundVariabilityType(); }
    | TOKEN_UINT8   { $$ = AtomicType::UniformUInt8->GetAsUnboundVariabilityType(); }
    | TOKEN_INT16   { $$ = AtomicType::UniformInt16->GetAsUnboundVariabilityType(); }
    | TOKEN_UINT16  { $$ = AtomicType::UniformUInt16->GetAsUnboundVariabilityType(); }
    | TOKEN_INT     { $$ = AtomicType::UniformInt32->GetAsUnboundVariabilityType(); }
    | TOKEN_UINT    { $$ = AtomicType::UniformUInt32->GetAsUnboundVariabilityType(); }
    | TOKEN_FLOAT16 { $$ = AtomicType::UniformFloat16->GetAsUnboundVariabilityType(); }
    | TOKEN_FLOAT   { $$ = AtomicType::UniformFloat->GetAsUnboundVariabilityType(); }
    | TOKEN_DOUBLE  { $$ = AtomicType::UniformDouble->GetAsUnboundVariabilityType(); }
    | TOKEN_INT64   { $$ = AtomicType::UniformInt64->GetAsUnboundVariabilityType(); }
    | TOKEN_UINT64  { $$ = AtomicType::UniformUInt64->GetAsUnboundVariabilityType(); }
    ;

short_vec_specifier
    : type_specifier '<' int_constant '>'
    {
        $$ = $1 ? new VectorType($1, (int32_t)$3) : nullptr;
    }
    | type_specifier '<' TOKEN_IDENTIFIER '>'
    {
        Symbol* s = new Symbol(*$<stringVal>3, Union(@1, @3), Symbol::SymbolKind::TemplateNonTypeParm, AtomicType::UniformInt32->GetAsConstType());
        lCleanUpString($3);
        $$ = $1 ? new VectorType($1, s) : nullptr;
    }
    ;

struct_or_union_name
    : TOKEN_IDENTIFIER
    {
        $$ = strdup(yytext);
        lCleanUpString($1);
    }
    | TOKEN_TYPE_NAME
    {
        $$ = strdup(yytext);
        lCleanUpString($1);
    }
    ;

struct_or_union_and_name
    : struct_or_union struct_or_union_name
      {
          const Type *st = m->symbolTable->LookupType($2);
          if (st == nullptr) {
              UndefinedStructType* ust = new UndefinedStructType($2, Variability::Unbound, false, @2);
              ust->RegisterInStructTypeMap();
              m->symbolTable->AddType($2, ust, @2);
              $$ = ust;
          } else {
              if (CastType<StructType>(st) == nullptr &&
                  CastType<UndefinedStructType>(st) == nullptr) {
                  Error(@2, "Type \"%s\" is not a struct type! (%s)", $2,
                        st->GetString().c_str());
                  $$ = nullptr;
              }
              else
                  $$ = st;
         }
         // allocated by strdup in struct_or_union_name
         free((char*)$2);
      }
    ;

struct_or_union_specifier
    : struct_or_union_and_name
    // We explicitly use here precedence to lower this rule priority in comparison
    // with the next one that has attribute_specifier (starting with TOKEN_ATTRIBUTE).
    // This is needed to avoid shift/reduce conflict, although the default
    // behavior (shifting) does same in our situation, but it is better to be
    // explicit here.
    | struct_or_union_and_name '{' struct_declaration_list '}' %prec STRUCT_WITHOUT_ATTRIBUTE
      {
          if ($3 != nullptr) {
              const std::string &name = CastType<StructType>($1) ? CastType<StructType>($1)->GetStructName()
                                                                 : CastType<UndefinedStructType>($1)->GetStructName();
              $$ = lCreateStructType(name, $3, nullptr, @1);
              // struct_declaration_list returns a vector that is not needed anymore.
              delete $3;
          } else {
              $$ = nullptr;
          }
      }
    | struct_or_union_and_name '{' struct_declaration_list '}' attribute_specifier
      {
          if ($3 != nullptr) {
              const std::string &name = CastType<StructType>($1) ? CastType<StructType>($1)->GetStructName()
                                                                 : CastType<UndefinedStructType>($1)->GetStructName();
              $$ = lCreateStructType(name, $3, $5, @1);
              // attribute_specifier returns an object that is not needed anymore.
              delete $5;
          } else {
              $$ = nullptr;
          }
          // struct_declaration_list returns a vector that is not needed anymore.
          delete $3;
      }
    | struct_or_union '{' struct_declaration_list '}' %prec STRUCT_WITHOUT_ATTRIBUTE
      {
          if ($3 != nullptr) {
              $$ = lCreateStructType("", $3, nullptr, @1);
              // struct_declaration_list returns a vector that is not needed anymore.
              delete $3;
          } else {
              $$ = nullptr;
          }
      }
    | struct_or_union '{' struct_declaration_list '}' attribute_specifier
      {
          if ($3 != nullptr) {
              $$ = lCreateStructType("", $3, $5, @1);
              // struct_declaration_list returns a vector that is not needed anymore.
              delete $3;
          } else {
              $$ = nullptr;
          }
          // attribute_specifier returns an object that is not needed anymore.
          delete $5;
      }
    | struct_or_union '{' '}' %prec STRUCT_WITHOUT_ATTRIBUTE
      {
          $$ = lCreateStructType("", nullptr, nullptr, @1);
      }
    | struct_or_union '{' '}' attribute_specifier
      {
          $$ = lCreateStructType("", nullptr, $4, @1);
          // attribute_specifier returns an object that is not needed anymore.
          delete $4;
      }
    | struct_or_union_and_name '{' '}' %prec STRUCT_WITHOUT_ATTRIBUTE
      {
          const std::string &name = CastType<StructType>($1) ? CastType<StructType>($1)->GetStructName()
                                                             : CastType<UndefinedStructType>($1)->GetStructName();
          $$ = lCreateStructType(name, nullptr, nullptr, @1);
      }
    | struct_or_union_and_name '{' '}' attribute_specifier
      {
          const std::string &name = CastType<StructType>($1) ? CastType<StructType>($1)->GetStructName()
                                                             : CastType<UndefinedStructType>($1)->GetStructName();
          $$ = lCreateStructType(name, nullptr, $4, @1);
          // attribute_specifier returns an object that is not needed anymore.
          delete $4;
      }
    ;

struct_or_union
    : TOKEN_STRUCT
    ;

struct_declaration_list
    : struct_declaration
      {
          std::vector<StructDeclaration *> *sdl = new std::vector<StructDeclaration *>;
          if ($1 != nullptr)
              sdl->push_back($1);
          $$ = sdl;
      }
    | struct_declaration_list struct_declaration
      {
          std::vector<StructDeclaration *> *sdl = (std::vector<StructDeclaration *> *)$1;
          if (sdl == nullptr) {
              AssertPos(@1, m->errorCount > 0);
              sdl = new std::vector<StructDeclaration *>;
          }
          if ($2 != nullptr)
              sdl->push_back($2);
          $$ = sdl;
      }
    ;

struct_declaration
    : specifier_qualifier_list struct_declarator_list ';'
      { $$ = ($1 != nullptr && $2 != nullptr) ? new StructDeclaration($1, $2) : nullptr; }
    ;

specifier_qualifier_list
    : type_specifier specifier_qualifier_list
    | type_specifier
    | short_vec_specifier
    | type_qualifier specifier_qualifier_list
    {
        if ($2 != nullptr) {
            if ($1 == TYPEQUAL_UNIFORM) {
                if ($2->IsVoidType()) {
                    Error(@1, "\"uniform\" qualifier is illegal with \"void\" type.");
                    $$ = nullptr;
                }
                else
                    $$ = $2->GetAsUniformType();
            }
            else if ($1 == TYPEQUAL_VARYING) {
                if ($2->IsVoidType()) {
                    Error(@1, "\"varying\" qualifier is illegal with \"void\" type.");
                    $$ = nullptr;
                }
                else
                    $$ = $2->GetAsVaryingType();
            }
            else if ($1 == TYPEQUAL_CONST)
                $$ = $2->GetAsConstType();
            else if ($1 == TYPEQUAL_SIGNED) {
                const Type *t = $2->GetAsSignedType();
                if (t)
                    $$ = t;
                else {
                    Error(@1, "Can't apply \"signed\" qualifier to \"%s\" type.",
                          $2->ResolveUnboundVariability(Variability::Varying)->GetString().c_str());
                    $$ = $2;
                }
            }
            else if ($1 == TYPEQUAL_UNSIGNED) {
                const Type *t = $2->GetAsUnsignedType();
                if (t)
                    $$ = t;
                else {
                    Error(@1, "Can't apply \"unsigned\" qualifier to \"%s\" type. Ignoring.",
                          $2->ResolveUnboundVariability(Variability::Varying)->GetString().c_str());
                    $$ = $2;
                }
            }
            else if ($1 == TYPEQUAL_INLINE) {
                Error(@1, "\"inline\" qualifier is illegal outside of "
                      "function declarations.");
                $$ = $2;
            }
            else if ($1 == TYPEQUAL_NOINLINE) {
                Error(@1, "\"noinline\" qualifier is illegal outside of "
                      "function declarations.");
                $$ = $2;
            }
            else if ($1 == TYPEQUAL_VECTORCALL) {
                Error(@1, "\"__vectorcall\" qualifier is illegal outside of "
                      "function declarations.");
                $$ = $2;
            }
            else if ($1 == TYPEQUAL_REGCALL) {
                Error(@1, "\"__regcall\" qualifier is illegal outside of "
                      "function declarations.");
                $$ = $2;
            }
            else if ($1 == TYPEQUAL_TASK) {
                Error(@1, "\"task\" qualifier is illegal outside of "
                      "function declarations.");
                $$ = $2;
            }
            else if ($1 == TYPEQUAL_UNMASKED) {
                Error(@1, "\"unmasked\" qualifier is illegal outside of "
                      "function declarations.");
                $$ = $2;
            }
            else if ($1 == TYPEQUAL_EXPORT) {
                Error(@1, "\"export\" qualifier is illegal outside of "
                      "function declarations.");
                $$ = $2;
            }
            else
                FATAL("Unhandled type qualifier in parser.");
        }
        else {
            if (m->errorCount == 0)
                Error(@1, "Lost type qualifier in parser.");
            $$ = nullptr;
        }
    }
    ;


struct_declarator_list
    : struct_declarator
      {
          std::vector<Declarator *> *sdl = new std::vector<Declarator *>;
          if ($1 != nullptr)
              sdl->push_back($1);
          $$ = sdl;
      }
    | struct_declarator_list ',' struct_declarator
      {
          std::vector<Declarator *> *sdl = (std::vector<Declarator *> *)$1;
          if (sdl == nullptr) {
              AssertPos(@1, m->errorCount > 0);
              sdl = new std::vector<Declarator *>;
          }
          if ($3 != nullptr)
              sdl->push_back($3);
          $$ = sdl;
      }
    ;

struct_declarator
    : declarator { $$ = $1; }
/* bitfields
    | ':' constant_expression
    | declarator ':' constant_expression
*/
    ;

enum_identifier
    : TOKEN_IDENTIFIER
      {
          $$ = strdup(yytext);
          lCleanUpString($1);
      }
    ;

enum_specifier
    : TOKEN_ENUM '{' enumerator_list '}'
      {
          $$ = lCreateEnumType(nullptr, $3, @1);
          // enumerator_list returns aux vector that is not needed anymore.
          delete $3;
      }
    | TOKEN_ENUM enum_identifier '{' enumerator_list '}'
      {
          $$ = lCreateEnumType($2, $4, @2);
          // allocated by strdup in enum_identifier
          free((char*)$2);

          // enumerator_list returns aux vector that is not needed anymore.
          delete $4;
      }
    | TOKEN_ENUM '{' enumerator_list ',' '}'
      {
          $$ = lCreateEnumType(nullptr, $3, @1);
          // enumerator_list returns aux vector that is not needed anymore.
          delete $3;
      }
    | TOKEN_ENUM enum_identifier '{' enumerator_list ',' '}'
      {
          $$ = lCreateEnumType($2, $4, @2);
          // allocated by strdup in enum_identifier
          free((char*)$2);

          // enumerator_list returns aux vector that is not needed anymore.
          delete $4;
      }
    | TOKEN_ENUM enum_identifier
      {
          const Type *type = m->symbolTable->LookupType($2);
          if (type == nullptr) {
              std::vector<std::string> alternates = m->symbolTable->ClosestEnumTypeMatch($2);
              std::string alts = lGetAlternates(alternates);
              Error(@2, "Enum type \"%s\" unknown.%s", $2, alts.c_str());
              $$ = nullptr;
          }
          else {
              const EnumType *enumType = CastType<EnumType>(type);
              if (enumType == nullptr) {
                  Error(@2, "Type \"%s\" is not an enum type (%s).", $2,
                        type->GetString().c_str());
                  $$ = nullptr;
              }
              else
                  $$ = enumType;
          }
          // allocated by strdup in enum_identifier
          free((char*)$2);
      }
    ;

enumerator_list
    : enumerator
      {
          if ($1 == nullptr)
              $$ = nullptr;
          else {
              std::vector<Symbol *> *el = new std::vector<Symbol *>;
              el->push_back($1);
              $$ = el;
          }
      }
    | enumerator_list ',' enumerator
      {
          std::vector<Symbol *> *symList = $1;
          if (symList == nullptr) {
              AssertPos(@1, m->errorCount > 0);
              symList = new std::vector<Symbol *>;
          }
          if ($3 != nullptr)
              symList->push_back($3);
          $$ = symList;
      }
    ;

enumerator
    : enum_identifier
      {
          $$ = new Symbol($1, @1, Symbol::SymbolKind::Enumerator);
          // allocated by strdup in enum_identifier
          free((char*)$1);
      }
    | enum_identifier '=' constant_expression
      {
          std::variant<std::monostate, int, Symbol*> value;
          if ($1 != nullptr && $3 != nullptr &&
              lGetConstantIntOrSymbol($3, &value, @3, "Enumerator value")) {
              Symbol *sym = new Symbol($1, @1, Symbol::SymbolKind::Enumerator);

              // Check if value holds an int
              if (std::holds_alternative<int>(value)) {
                  int intValue = std::get<int>(value);
                  sym->constValue = new ConstExpr(AtomicType::UniformUInt32->GetAsConstType(),
                                                  (uint32_t)intValue, @3);
              } else {
                  Error(@3, "Enumerator value must be a compile-time constant.");
                  $$ = nullptr;
              }
              $$ = sym;
          }
          else {
              $$ = nullptr;
          }

          // allocated by strdup in enum_identifier
          free((char*)$1);
      }
    ;

type_qualifier
    : TOKEN_CONST         { $$ = TYPEQUAL_CONST; }
    | TOKEN_UNIFORM       { $$ = TYPEQUAL_UNIFORM; }
    | TOKEN_VARYING       { $$ = TYPEQUAL_VARYING; }
    | TOKEN_TASK          { $$ = TYPEQUAL_TASK; }
    | TOKEN_UNMASKED      { $$ = TYPEQUAL_UNMASKED; }
    | TOKEN_EXPORT        { $$ = TYPEQUAL_EXPORT; }
    | TOKEN_INLINE        { $$ = TYPEQUAL_INLINE; }
    | TOKEN_NOINLINE      { $$ = TYPEQUAL_NOINLINE; }
    | TOKEN_VECTORCALL    { $$ = TYPEQUAL_VECTORCALL; }
    | TOKEN_REGCALL       { $$ = TYPEQUAL_REGCALL; }
    | TOKEN_SIGNED        { $$ = TYPEQUAL_SIGNED; }
    | TOKEN_UNSIGNED      { $$ = TYPEQUAL_UNSIGNED; }
    ;

type_qualifier_list
    : type_qualifier
    {
        $$ = $1;
    }
    | type_qualifier_list type_qualifier
    {
        $$ = $1 | $2;
    }
    ;

declarator
    : pointer direct_declarator
    {
        if ($1 != nullptr) {
            Declarator *tail = $1;
            while (tail->child != nullptr)
               tail = tail->child;
            tail->child = $2;
            $$ = $1;
        }
        else
            $$ = nullptr;
    }
    | reference direct_declarator
    {
        if ($1 != nullptr) {
            Declarator *tail = $1;
            while (tail->child != nullptr)
               tail = tail->child;
            tail->child = $2;
            $$ = $1;
        }
        else
            $$ = nullptr;
    }
    | direct_declarator
    ;

int_constant
    : TOKEN_INT8_CONSTANT { $$ = yylval.intVal; }
    | TOKEN_UINT8_CONSTANT { $$ = yylval.intVal; }
    | TOKEN_INT16_CONSTANT { $$ = yylval.intVal; }
    | TOKEN_UINT16_CONSTANT { $$ = yylval.intVal; }
    | TOKEN_INT32_CONSTANT { $$ = yylval.intVal; }
    | TOKEN_UINT32_CONSTANT { $$ = yylval.intVal; }
    | TOKEN_INT64_CONSTANT { $$ = yylval.intVal; }
    | TOKEN_UINT64_CONSTANT { $$ = yylval.intVal; }
    ;

direct_declarator
    : TOKEN_IDENTIFIER
      {
          Declarator *d = new Declarator(DK_BASE, @1);
          d->name = yytext;
          $$ = d;
          lCleanUpString($1);
      }
    // For the purpose of declaration, template_name token is no different from identifier token,
    // it needs to be processed in the same way. Semantic checks will be done later.
    | TOKEN_TEMPLATE_NAME
      {
          Declarator *d = new Declarator(DK_BASE, @1);
          d->name = yytext;
          $$ = d;
          lCleanUpString($1);
      }
    | '(' declarator ')'
    {
        $$ = $2;
    }
    | direct_declarator '[' constant_expression ']'
    {
        std::variant<std::monostate, int, Symbol*> size;
        if ($1 != nullptr && lGetConstantIntOrSymbol($3, &size, @3, "Array dimension")) {
            // Check if size holds an int
            if (std::holds_alternative<int>(size)) {
                int intValue = std::get<int>(size);
                if (intValue < 0) {
                    Error(@3, "Array dimension must be non-negative.");
                    $$ = nullptr;
                } else {
                    Declarator *d = new Declarator(DK_ARRAY, Union(@1, @4));
                    d->arraySize = intValue;
                    d->child = $1;
                    $$ = d;
                }
            } else if (std::holds_alternative<Symbol*>(size)) {
                // Handle the case where size holds a Symbol*
                Symbol* symbolValuePtr = std::get<Symbol*>(size);
                Declarator *d = new Declarator(DK_ARRAY, Union(@1, @4));
                d->arraySize = symbolValuePtr;
                d->child = $1;
                $$ = d;
            } else {
                $$ = nullptr;
            }
        }
        else {
            $$ = nullptr;
        }
    }
    | direct_declarator '[' ']'
    {
        if ($1 != nullptr) {
            Declarator *d = new Declarator(DK_ARRAY, Union(@1, @3));
            d->arraySize = 0; // unsize
            d->child = $1;
            $$ = d;
        }
        else
            $$ = nullptr;
    }
    | direct_declarator '[' error ']'
    {
         $$ = nullptr;
    }
    | direct_declarator '(' parameter_type_list ')'
      {
          if ($1 != nullptr) {
              Declarator *d = new Declarator(DK_FUNCTION, Union(@1, @4));
              d->child = $1;
              if ($3 != nullptr) {
                  d->functionParams = *$3;
                  // parameter_type_list returns vector of Declarations that is not needed anymore.
                  delete $3;
              }
              $$ = d;
          }
          else
              $$ = nullptr;
      }
    | direct_declarator '(' ')'
      {
          if ($1 != nullptr) {
              Declarator *d = new Declarator(DK_FUNCTION, Union(@1, @3));
              d->child = $1;
              $$ = d;
          }
          else
              $$ = nullptr;
      }
    | direct_declarator '(' error ')'
    {
        $$ = nullptr;
    }
    ;


pointer
    : '*'
    {
        $$ = new Declarator(DK_POINTER, @1);
    }
    | '*' type_qualifier_list
      {
          Declarator *d = new Declarator(DK_POINTER, Union(@1, @2));
          d->typeQualifiers = $2;
          $$ = d;
      }
    | '*' pointer
      {
          Declarator *d = new Declarator(DK_POINTER, Union(@1, @2));
          d->child = $2;
          $$ = d;
      }
    | '*' type_qualifier_list pointer
      {
          Declarator *d = new Declarator(DK_POINTER, Union(@1, @3));
          d->typeQualifiers = $2;
          d->child = $3;
          $$ = d;
      }
    ;


reference
    : '&'
    {
        $$ = new Declarator(DK_REFERENCE, @1);
    }
    ;


parameter_type_list
    : parameter_list { $$ = $1; }
    ;

parameter_list
    : parameter_declaration
    {
        std::vector<Declaration *> *dl = new std::vector<Declaration *>;
        if ($1 != nullptr)
            dl->push_back($1);
        $$ = dl;
    }
    | parameter_list ',' parameter_declaration
    {
        std::vector<Declaration *> *dl = (std::vector<Declaration *> *)$1;
        if (dl == nullptr)
            dl = new std::vector<Declaration *>;
        if ($3 != nullptr)
            dl->push_back($3);
        $$ = dl;
    }
    | error ','
    {
        lSuggestParamListAlternates();
        $$ = nullptr;
    }
    ;

parameter_declaration
    : declaration_specifiers declarator
    {
        $$ = new Declaration($1, $2);
    }
    | declaration_specifiers declarator '=' initializer
    {
        if ($1 != nullptr && $2 != nullptr) {
            $2->initExpr = $4;
            $$ = new Declaration($1, $2);
        }
        else
            $$ = nullptr;
    }
    | declaration_specifiers abstract_declarator
    {
        if ($1 != nullptr && $2 != nullptr)
            $$ = new Declaration($1, $2);
        else
            $$ = nullptr;
    }
    | declaration_specifiers
    {
        if ($1 == nullptr)
            $$ = nullptr;
        else
            $$ = new Declaration($1);
    }
    ;

/* K&R?
identifier_list
    : IDENTIFIER
    | identifier_list ',' IDENTIFIER
    ;
*/

type_name
    : specifier_qualifier_list
    | specifier_qualifier_list abstract_declarator
    {
        if ($1 == nullptr || $2 == nullptr)
            $$ = nullptr;
        else {
            $2->InitFromType($1, nullptr);
            $$ = $2->type;
        }
    }
    ;

abstract_declarator
    : pointer
      {
          $$ = $1;
      }
    | direct_abstract_declarator
    | pointer direct_abstract_declarator
      {
          if ($2 == nullptr)
              $$ = nullptr;
          else {
              Declarator *d = new Declarator(DK_POINTER, Union(@1, @2));
              d->child = $2;
              $$ = d;
          }
      }
    | reference
      {
          $$ = new Declarator(DK_REFERENCE, @1);
      }
    | reference direct_abstract_declarator
      {
          if ($2 == nullptr)
              $$ = nullptr;
          else {
              Declarator *d = new Declarator(DK_REFERENCE, Union(@1, @2));
              d->child = $2;
              $$ = d;
          }
      }
    ;

direct_abstract_declarator
    : '(' abstract_declarator ')'
      { $$ = $2; }
    | '[' ']'
      {
          Declarator *d = new Declarator(DK_ARRAY, Union(@1, @2));
          d->arraySize = 0;
          $$ = d;
      }
    | '[' constant_expression ']'
      {
        std::variant<std::monostate, int, Symbol*> size;
        if ($2 != nullptr && lGetConstantIntOrSymbol($2, &size, @2, "Array dimension")) {
            // Check if size holds an int
            if (std::holds_alternative<int>(size)) {
                int intValue = std::get<int>(size);
                if (intValue < 0) {
                    Error(@2, "Array dimension must be non-negative.");
                    $$ = nullptr;
                } else {
                    Declarator *d = new Declarator(DK_ARRAY, Union(@1, @3));
                    d->arraySize = intValue;
                    $$ = d;
                }
            } else if (std::holds_alternative<Symbol*>(size)) {
                // Handle the case where size holds a Symbol*
                Symbol* symbolValuePtr = std::get<Symbol*>(size);
                Declarator *d = new Declarator(DK_ARRAY, Union(@1, @3));
                d->arraySize = symbolValuePtr;
                $$ = d;
            } else {
                $$ = nullptr;
            }
        }
        else {
            $$ = nullptr;
        }
      }
    | direct_abstract_declarator '[' ']'
      {
          if ($1 == nullptr)
              $$ = nullptr;
          else {
              Declarator *d = new Declarator(DK_ARRAY, Union(@1, @3));
              d->arraySize = 0;
              d->child = $1;
              $$ = d;
          }
      }
    | direct_abstract_declarator '[' constant_expression ']'
      {
          std::variant<std::monostate, int, Symbol*> size;
          if ($1 != nullptr && $3 != nullptr && lGetConstantIntOrSymbol($3, &size, @3, "Array dimension")) {
              // Check if size holds an int
              if (std::holds_alternative<int>(size)) {
                  int intValue = std::get<int>(size);
                  if (intValue < 0) {
                      Error(@3, "Array dimension must be non-negative.");
                      $$ = nullptr;
                  } else {
                      Declarator *d = new Declarator(DK_ARRAY, Union(@1, @4));
                      d->arraySize = intValue;
                      d->child = $1;
                      $$ = d;
                  }
              } else if (std::holds_alternative<Symbol*>(size)) {
                  // Handle the case where size holds a Symbol*
                  Symbol* symbolValuePtr = std::get<Symbol*>(size);
                  Declarator *d = new Declarator(DK_ARRAY, Union(@1, @4));
                  d->arraySize = symbolValuePtr;
                  d->child = $1;
                  $$ = d;
              } else {
                  $$ = nullptr;
              }
          }
          else {
              $$ = nullptr;
          }
      }
    | '(' ')'
      { $$ = new Declarator(DK_FUNCTION, Union(@1, @2)); }
    | '(' parameter_type_list ')'
      {
          Declarator *d = new Declarator(DK_FUNCTION, Union(@1, @3));
          if ($2 != nullptr) {
              d->functionParams = *$2;
              // parameter_type_list returns vector of Declarations that is not needed anymore.
              delete $2;
          }
          $$ = d;
      }
    | direct_abstract_declarator '(' ')'
      {
          if ($1 == nullptr)
              $$ = nullptr;
          else {
              Declarator *d = new Declarator(DK_FUNCTION, Union(@1, @3));
              d->child = $1;
              $$ = d;
          }
      }
    | direct_abstract_declarator '(' parameter_type_list ')'
      {
          if ($1 == nullptr)
              $$ = nullptr;
          else {
              Declarator *d = new Declarator(DK_FUNCTION, Union(@1, @4));
              d->child = $1;
              if ($3 != nullptr) {
                  d->functionParams = *$3;
                  // parameter_type_list returns vector of Declarations that is not needed anymore.
                  delete $3;
              }
              $$ = d;
          }
      }
    ;

initializer
    : assignment_expression
    | '{' initializer_list '}' { $$ = $2; }
    | '{' initializer_list ',' '}' { $$ = $2; }
    ;

initializer_list
    : initializer
      { $$ = new ExprList($1, @1); }
    | initializer_list ',' initializer
      {
          ExprList *exprList = $1;
          if (exprList == nullptr) {
              AssertPos(@1, m->errorCount > 0);
              exprList = new ExprList(@3);
          }
          exprList->exprs.push_back($3);
          exprList->pos = Union(exprList->pos, @3);
          $$ = exprList;
      }
    ;

pragma
    : TOKEN_PRAGMA
    {
        $$ = (yylval.pragmaAttributes);
    }
    ;

attributed_statement
    : pragma attributed_statement
    {
        if (($1->aType == PragmaAttributes::AttributeType::pragmaloop) && ($2 != nullptr)) {
            std::pair<Globals::pragmaUnrollType, int> unrollVal = std::pair<Globals::pragmaUnrollType, int>($1->unrollType, $1->count);
            $2->SetLoopAttribute(unrollVal);
        }
        $$ = $2;
        // deallocate yylval.pragmaAttributes returned from pragma and allocated in lPragmaUnroll
        delete $1;
    }
    | statement
    ;

statement
    : labeled_statement
    | compound_statement
    | expression_statement
    | selection_statement
    | iteration_statement
    | jump_statement
    | declaration_statement
    | print_statement
    | assert_statement
    | sync_statement
    | delete_statement
    | unmasked_statement
    | error ';'
    {
        lSuggestBuiltinAlternates();
        $$ = nullptr;
    }
    ;

labeled_statement
    : goto_identifier ':' attributed_statement
    {
        $$ = new LabeledStmt($1, $3, @1);
        // allocated by strdup in goto_identifier
        free((char*)$1);
    }
    | TOKEN_CASE constant_expression ':' attributed_statement
      {
          std::variant<std::monostate, int, Symbol*> value;
          if ($2 != nullptr &&
              lGetConstantIntOrSymbol($2, &value, @2, "Case statement value")) {
              // Check if value holds an int
              if (std::holds_alternative<int>(value)) {
                  int intValue = std::get<int>(value);
                  $$ = new CaseStmt(intValue, $4, Union(@1, @2));
              } else {
                  Error(@2, "Case statement value must be a compile-time constant or template non-type parameter.");
                  $$ = nullptr;
              }
          }
          else {
              $$ = nullptr;
          }
      }
    | TOKEN_DEFAULT ':' attributed_statement
      { $$ = new DefaultStmt($3, @1); }
    ;

start_scope
    : '{' { m->symbolTable->PushScope(); }
    ;

end_scope
    : '}' { m->symbolTable->PopScope(); }
    ;

compound_statement
    : '{' '}' { $$ = nullptr; }
    | start_scope statement_list end_scope { $$ = $2; }
    ;

statement_list
    : attributed_statement
      {
          StmtList *sl = new StmtList(@1);
          sl->Add($1);
          $$ = sl;
      }
    | statement_list attributed_statement
      {
          StmtList *sl = (StmtList *)$1;
          if (sl == nullptr) {
              AssertPos(@1, m->errorCount > 0);
              sl = new StmtList(@2);
          }
          sl->Add($2);
          $$ = sl;
      }
    ;

expression_statement
    : ';' { $$ = nullptr; }
    | expression ';' { $$ = $1 ? new ExprStmt($1, @1) : nullptr; }
    ;

if_scope
    : TOKEN_IF { m->symbolTable->PushScope(); }
    ;

cif_scope
    : TOKEN_CIF { m->symbolTable->PushScope(); }
    ;

selection_statement
    : if_scope '(' expression ')' attributed_statement
      {
        $$ = new IfStmt($3, $5, nullptr, false, @1);
        m->symbolTable->PopScope();
      }
    | if_scope '(' expression ')' attributed_statement TOKEN_ELSE
      {
        m->symbolTable->PopScope();
        m->symbolTable->PushScope();
      }
      attributed_statement
      {
        $$ = new IfStmt($3, $5, $8, false, @1);
        m->symbolTable->PopScope();
      }
    | cif_scope '(' expression ')' attributed_statement
      {
        $$ = new IfStmt($3, $5, nullptr, true, @1);
        m->symbolTable->PopScope();
      }
    | cif_scope '(' expression ')' attributed_statement TOKEN_ELSE
      {
        m->symbolTable->PopScope();
        m->symbolTable->PushScope();
      }
      attributed_statement
      {
        $$ = new IfStmt($3, $5, $8, true, @1);
        m->symbolTable->PopScope();
      }
    | TOKEN_SWITCH '(' expression ')' attributed_statement
      { $$ = new SwitchStmt($3, $5, @1); }
    ;

for_test
    : ';'
      { $$ = nullptr; }
    | expression ';'
      { $$ = $1; }
    ;

for_init_statement
    : expression_statement
    | declaration_statement
    ;

while_scope
    : TOKEN_WHILE { m->symbolTable->PushScope(); }
    ;

cwhile_scope
    : TOKEN_CWHILE { m->symbolTable->PushScope(); }
    ;

do_scope
    : TOKEN_DO { m->symbolTable->PushScope(); }
    ;

cdo_scope
    : TOKEN_CDO { m->symbolTable->PushScope(); }
    ;

for_scope
    : TOKEN_FOR { m->symbolTable->PushScope(); }
    ;

cfor_scope
    : TOKEN_CFOR { m->symbolTable->PushScope(); }
    ;

foreach_scope
    : TOKEN_FOREACH { m->symbolTable->PushScope(); }
    ;

foreach_tiled_scope
    : TOKEN_FOREACH_TILED { m->symbolTable->PushScope(); }
    ;

foreach_identifier
    : TOKEN_IDENTIFIER
    {
        $$ = new Symbol(yytext, @1, Symbol::SymbolKind::Variable, AtomicType::VaryingInt32->GetAsConstType());
        lCleanUpString($1);
    }
    ;

foreach_active_scope
    : TOKEN_FOREACH_ACTIVE { m->symbolTable->PushScope(); }
    ;

foreach_active_identifier
    : TOKEN_IDENTIFIER
    {
        $$ = new Symbol(yytext, @1, Symbol::SymbolKind::Variable, AtomicType::UniformInt64->GetAsConstType());
        lCleanUpString($1);
    }
    ;

integer_dotdotdot
    : TOKEN_INT32DOTDOTDOT_CONSTANT {
        $$ = new ConstExpr(AtomicType::UniformInt32->GetAsConstType(),
                           (int32_t)yylval.intVal, @1);
    }
    | TOKEN_UINT32DOTDOTDOT_CONSTANT {
        $$ = new ConstExpr(AtomicType::UniformUInt32->GetAsConstType(),
                           (uint32_t)yylval.intVal, @1);
    }
    | TOKEN_INT64DOTDOTDOT_CONSTANT {
        $$ = new ConstExpr(AtomicType::UniformInt64->GetAsConstType(),
                           (int64_t)yylval.intVal, @1);
    }
    | TOKEN_UINT64DOTDOTDOT_CONSTANT {
        $$ = new ConstExpr(AtomicType::UniformUInt64->GetAsConstType(),
                           (uint64_t)yylval.intVal, @1);
    }
    ;

foreach_dimension_specifier
    : foreach_identifier '=' assignment_expression TOKEN_DOTDOTDOT assignment_expression
    {
        $$ = new ForeachDimension($1, $3, $5);
    }
    | foreach_identifier '=' integer_dotdotdot assignment_expression
    {
        $$ = new ForeachDimension($1, $3, $4);
    }
    ;

foreach_dimension_list
    : foreach_dimension_specifier
    {
        $$ = new std::vector<ForeachDimension *>;
        $$->push_back($1);
    }
    | foreach_dimension_list ',' foreach_dimension_specifier
    {
        std::vector<ForeachDimension *> *dv = $1;
        if (dv == nullptr) {
            AssertPos(@1, m->errorCount > 0);
            dv = new std::vector<ForeachDimension *>;
        }
        if ($3 != nullptr)
            dv->push_back($3);
        $$ = dv;
    }
    ;

foreach_unique_scope
    : TOKEN_FOREACH_UNIQUE { m->symbolTable->PushScope(); }
    ;

foreach_unique_identifier
    : TOKEN_IDENTIFIER
      {
          $$ = strdup($1->c_str());
          lCleanUpString($1);
      }
    ;

iteration_statement
    : while_scope '(' expression ')' attributed_statement
      { $$ = new ForStmt(nullptr, $3, nullptr, $5, false, @1);
        m->symbolTable->PopScope();
      }
    | cwhile_scope '(' expression ')' attributed_statement
      { $$ = new ForStmt(nullptr, $3, nullptr, $5, true, @1);
        m->symbolTable->PopScope();
      }
    | do_scope attributed_statement TOKEN_WHILE '(' expression ')' ';'
      { $$ = new DoStmt($5, $2, false, @1);
        m->symbolTable->PopScope();
      }
    | cdo_scope attributed_statement TOKEN_WHILE '(' expression ')' ';'
      { $$ = new DoStmt($5, $2, true, @1);
        m->symbolTable->PopScope();
      }
    | for_scope '(' for_init_statement for_test ')' attributed_statement
      { $$ = new ForStmt($3, $4, nullptr, $6, false, @1);
        m->symbolTable->PopScope();
      }
    | for_scope '(' for_init_statement for_test expression ')' attributed_statement
      { $$ = new ForStmt($3, $4, new ExprStmt($5, @5), $7, false, @1);
        m->symbolTable->PopScope();
      }
    | cfor_scope '(' for_init_statement for_test ')' attributed_statement
      { $$ = new ForStmt($3, $4, nullptr, $6, true, @1);
        m->symbolTable->PopScope();
      }
    | cfor_scope '(' for_init_statement for_test expression ')' attributed_statement
      { $$ = new ForStmt($3, $4, new ExprStmt($5, @5), $7, true, @1);
        m->symbolTable->PopScope();
      }
    | foreach_scope '(' foreach_dimension_list ')'
     {
         std::vector<ForeachDimension *> *dims = $3;
         if (dims == nullptr) {
             AssertPos(@3, m->errorCount > 0);
             dims = new std::vector<ForeachDimension *>;
         }
         for (unsigned int i = 0; i < dims->size(); ++i)
             m->symbolTable->AddVariable((*dims)[i]->sym);
     }
     attributed_statement
     {
         std::vector<ForeachDimension *> *dims = $3;
         if (dims == nullptr) {
             AssertPos(@3, m->errorCount > 0);
             dims = new std::vector<ForeachDimension *>;
         }

         std::vector<Symbol *> syms;
         std::vector<Expr *> begins, ends;
         for (unsigned int i = 0; i < dims->size(); ++i) {
             syms.push_back((*dims)[i]->sym);
             begins.push_back((*dims)[i]->beginExpr);
             ends.push_back((*dims)[i]->endExpr);
         }
         $$ = new ForeachStmt(syms, begins, ends, $6, false, @1);
         m->symbolTable->PopScope();

         // deallocate ForeachDimension elements allocated in foreach_dimension_specifier
         for (unsigned int i = 0; i < dims->size(); ++i)
             delete (*dims)[i];

         // deallocate std::vector<ForeachDimension*> allocated in foreach_dimension_list
         delete dims;
     }
    | foreach_tiled_scope '(' foreach_dimension_list ')'
     {
         std::vector<ForeachDimension *> *dims = $3;
         if (dims == nullptr) {
             AssertPos(@3, m->errorCount > 0);
             dims = new std::vector<ForeachDimension *>;
         }

         for (unsigned int i = 0; i < dims->size(); ++i)
             m->symbolTable->AddVariable((*dims)[i]->sym);
     }
     attributed_statement
     {
         std::vector<ForeachDimension *> *dims = $3;
         if (dims == nullptr) {
             AssertPos(@1, m->errorCount > 0);
             dims = new std::vector<ForeachDimension *>;
         }

         std::vector<Symbol *> syms;
         std::vector<Expr *> begins, ends;
         for (unsigned int i = 0; i < dims->size(); ++i) {
             syms.push_back((*dims)[i]->sym);
             begins.push_back((*dims)[i]->beginExpr);
             ends.push_back((*dims)[i]->endExpr);
         }
         $$ = new ForeachStmt(syms, begins, ends, $6, true, @1);
         m->symbolTable->PopScope();

         // deallocate ForeachDimension elements allocated in foreach_dimension_specifier
         for (unsigned int i = 0; i < dims->size(); ++i)
             delete (*dims)[i];

         // deallocate std::vector<ForeachDimension*> allocated in foreach_dimension_list
         delete dims;
     }
    | foreach_active_scope '(' foreach_active_identifier ')'
     {
         if ($3 != nullptr)
             m->symbolTable->AddVariable($3);
     }
     attributed_statement
     {
         $$ = new ForeachActiveStmt($3, $6, Union(@1, @4));
         m->symbolTable->PopScope();
     }
    | foreach_unique_scope '(' foreach_unique_identifier TOKEN_IN
         expression ')'
     {
         Expr *expr = $5;
         const Type *type;
         if (expr != nullptr &&
             (expr = TypeCheck(expr)) != nullptr &&
             (type = expr->GetType()) != nullptr) {
             const Type *iterType = type->GetAsUniformType()->GetAsConstType();
             Symbol *sym = new Symbol($3, @3, Symbol::SymbolKind::Variable, iterType);
             m->symbolTable->AddVariable(sym);
         }
     }
     attributed_statement
     {
         $$ = new ForeachUniqueStmt($3, $5, $8, @1);
         m->symbolTable->PopScope();

         // allocated by strdup in foreach_unique_identifier
         free((char*)$3);
     }
    ;

goto_identifier
    : TOKEN_IDENTIFIER
      {
          $$ = strdup($1->c_str());
          lCleanUpString($1);
      }
    ;

jump_statement
    : TOKEN_GOTO goto_identifier ';'
      {
          $$ = new GotoStmt($2, @1, @2);
          // allocated by strdup in goto_identifier
          free((char*)$2);
      }
    | TOKEN_CONTINUE ';'
      { $$ = new ContinueStmt(@1); }
    | TOKEN_BREAK ';'
      { $$ = new BreakStmt(@1); }
    | TOKEN_RETURN ';'
      { $$ = new ReturnStmt(nullptr, @1); }
    | TOKEN_RETURN expression ';'
      { $$ = new ReturnStmt($2, @1); }
    ;

sync_statement
    : TOKEN_SYNC ';'
      { $$ = new ExprStmt(new SyncExpr(@1), @1); }
    ;

delete_statement
    : TOKEN_DELETE expression ';'
    {
        $$ = new DeleteStmt($2, Union(@1, @2));
    }
    ;

unmasked_statement
    : TOKEN_UNMASKED '{' statement_list '}'
    {
        $$ = new UnmaskedStmt($3, @1);
    }
    ;

print_statement
    : TOKEN_PRINT '(' string_constant ')' ';'
      {
           $$ = new PrintStmt(*$3, nullptr, @1);
           // deallocate std::string of string_constant
           lCleanUpString($3);
      }
    | TOKEN_PRINT '(' string_constant ',' argument_expression_list ')' ';'
      {
           $$ = new PrintStmt(*$3, $5, @1);
           // deallocate std::string of string_constant
           lCleanUpString($3);
      }
    ;

assert_statement
    : TOKEN_ASSERT '(' string_constant ',' expression ')' ';'
      {
          $$ = new AssertStmt(*$3, $5, @1);
          // deallocate std::string of string_constant
          lCleanUpString($3);
      }
    ;

translation_unit
    : external_declaration
    | translation_unit external_declaration
    | error ';'
    ;

external_declaration
    : function_definition
    | template_function_declaration_or_definition
    | template_function_specialization
    | template_function_instantiation
    | TOKEN_EXTERN TOKEN_STRING_C_LITERAL '{' declaration '}'
    | TOKEN_EXTERN TOKEN_STRING_SYCL_LITERAL '{' declaration '}'
    | TOKEN_EXPORT '{' type_specifier_list '}' ';'
    {
        if ($3 != nullptr)
            m->AddExportedTypes(*$3);
    }
    | declaration
    {
        if ($1 != nullptr)
            for (unsigned int i = 0; i < $1->declarators.size(); ++i)
                lAddDeclaration($1->declSpecs, $1->declarators[i]);
    }
    | ';'
    ;

function_definition
    : declaration_specifiers declarator
    {
        lAddDeclaration($1, $2);
        m->symbolTable->PushScope();
        lAddFunctionParams($2);
        lAddMaskToSymbolTable(@2);
        if ($1->typeQualifiers & TYPEQUAL_TASK)
            lAddThreadIndexCountToSymbolTable(@2);
    }
    compound_statement
    {
        if ($2 != nullptr) {
            // FIXME: Next list is redundant, as it's done in lAddDeclaration()
            $2->InitFromDeclSpecs($1);
            const FunctionType *funcType = CastType<FunctionType>($2->type);
            if (funcType == nullptr)
                AssertPos(@1, m->errorCount > 0);
            else if ($1->storageClass.IsTypedef())
                Error(@1, "Illegal \"typedef\" provided with function definition.");
            else {
                Stmt *code = $4;
                if (code == nullptr) code = new StmtList(@4);
                m->AddFunctionDefinition($2->name, funcType, code);
            }
        }
        m->symbolTable->PopScope(); // push in lAddFunctionParams();
    }
/* function with no declared return type??
func(...)
    | declarator { lAddFunctionParams($1); } compound_statement
    {
        m->AddFunction(new DeclSpecs(XXX, $1, $3);
        m->symbolTable->PopScope(); // push in lAddFunctionParams();
    }
*/
    ;

template_type_parameter
    : TOKEN_TYPENAME TOKEN_IDENTIFIER
      {
          $$ = new TemplateTypeParmType(*$<stringVal>2, Variability::VarType::Unbound, false, Union(@1, @2));
          lCleanUpString($<stringVal>2);
      }
    | TOKEN_TYPENAME TOKEN_IDENTIFIER '=' type_specifier
      {
          $$ = new TemplateTypeParmType(*$<stringVal>2, Variability::VarType::Unbound, false, Union(@1, @2));
          lCleanUpString($<stringVal>2);
          // TODO: implement
          Error(@4, "Default values for template type parameters are not yet supported.");
      }
    ;

int_constant_type
    : TOKEN_INT8   { $$ = AtomicType::UniformInt8->GetAsConstType(); }
    | TOKEN_INT16  { $$ = AtomicType::UniformInt16->GetAsConstType(); }
    | TOKEN_INT    { $$ = AtomicType::UniformInt32->GetAsConstType(); }
    | TOKEN_INT64  { $$ = AtomicType::UniformInt64->GetAsConstType(); }
    | TOKEN_UINT8  { $$ = AtomicType::UniformUInt8->GetAsConstType(); }
    | TOKEN_UINT16 { $$ = AtomicType::UniformUInt16->GetAsConstType(); }
    | TOKEN_UINT   { $$ = AtomicType::UniformUInt32->GetAsConstType(); }
    | TOKEN_UINT64 { $$ = AtomicType::UniformUInt64->GetAsConstType(); }
    | TOKEN_BOOL   { $$ = AtomicType::UniformBool->GetAsConstType(); }
    ;

template_int_constant_type
    : TOKEN_UNIFORM int_constant_type { $$ = $2; }
    | int_constant_type { $$ = $1;}
    ;

template_int_parameter
    : template_int_constant_type TOKEN_IDENTIFIER
      {
          $$ = new Symbol(*$<stringVal>2, Union(@1, @2), Symbol::SymbolKind::TemplateNonTypeParm, $1);
          lCleanUpString($2);
      }
      | template_int_constant_type TOKEN_IDENTIFIER '=' int_constant
      {
          $$ = new Symbol(*$<stringVal>2, Union(@1, @2), Symbol::SymbolKind::TemplateNonTypeParm, $1);
          lCleanUpString($2);
          // TODO: implement
          Error(@4, "Default values for template non-type parameters are not yet supported.");
      }
    ;

template_enum_parameter
    : TOKEN_TYPE_NAME TOKEN_IDENTIFIER
      {
          const Type *type = m->symbolTable->LookupType($1->c_str());
          const EnumType *enumType = CastType<EnumType>(type);
          if (enumType == nullptr) {
            Error(@1, "Only enum types and integral types are allowed as non-type template parameters.");
          }
          $$ = new Symbol(*$<stringVal>2, Union(@1, @2), Symbol::SymbolKind::TemplateNonTypeParm, enumType->GetAsConstType()->GetAsUniformType());
          lCleanUpString($1);
          lCleanUpString($2);
      }

template_parameter
    : template_type_parameter
    {
        if ($1 != nullptr) {
            $$ = new TemplateParam($1);
        }
    }
    | template_int_parameter
    {
        if ($1 != nullptr) {
            $$ = new TemplateParam($1);
        }
    }
    | template_enum_parameter
    {
        if ($1 != nullptr) {
            $$ = new TemplateParam($1);
        }
    }
    ;

template_parameter_list
    : template_parameter
      {
          TemplateParms *list = new TemplateParms();
          if ($1 != nullptr) {
              list->Add($1);
          }
          $$ = list;
      }
    | template_parameter_list ',' template_parameter
      {
          TemplateParms *list = (TemplateParms *) $1;
          if (list == nullptr) {
              AssertPos(@1, m->errorCount > 0);
              list = new TemplateParms();
          }
          if ($3 != nullptr) {
              list->Add($3);
          }
          $$ = list;
      }
    ;

template_head
    : TOKEN_TEMPLATE '<' template_parameter_list '>'
      {
          $$ = $3;
      }
    ;

template_declaration
    : template_head
      {
          // Scope for template parameters definition
          m->symbolTable->PushScope();
          TemplateParms *list = (TemplateParms *) $1;
          for(size_t i = 0; i < list->GetCount(); i++) {
              std::string name = (*list)[i]->GetName();
              SourcePos pos = (*list)[i]->GetSourcePos();
              if ((*list)[i]->IsTypeParam()) {
                  m->AddTypeDef(name, (*list)[i]->GetTypeParam(), pos);
              } else if ((*list)[i]->IsNonTypeParam()) {
                  m->symbolTable->AddVariable((*list)[i]->GetNonTypeParam());
              }
          }
      }
      declaration_specifiers declarator
      {
          lAddTemplateDeclaration($1, $3, $4);
          lAddFunctionParams($4);
          lAddMaskToSymbolTable(@4);
          const FunctionType *ft = CastType<FunctionType>($4->type);
          // Creating a new TemplateSymbol just to pass it further seems to be a waste
          $$ = new TemplateSymbol($1, $4->name, ft, $3->storageClass, @4, false /*not used*/, false /*not used*/);
      }
    ;

template_function_declaration_or_definition
    : template_declaration ';'
      {
          // deallocate TemplateSymbol created in template_declaration
          delete $1;
          // End templates parameters definition scope
          m->symbolTable->PopScope();
      }
    | template_declaration compound_statement
      {
          if ($1 != nullptr) {
              Stmt *code = $2;
              if (code == nullptr) code = new StmtList(@2);
              m->AddFunctionTemplateDefinition($1->templateParms, $1->name, $1->type, code);
              // deallocate TemplateSymbol created in template_declaration
              delete $1;
          }

          // End templates parameters definition scope
          m->symbolTable->PopScope();
      }
    ;

template_argument
    : rate_qualified_type_specifier
    {
        $$ = new TemplateArg($1, @1);
    }
    // Ideally we should use here constant_expression, however, there is grammar ambiguitiy between
    // template_identifier '<' template_argument_list '>' in simple_template_id and
    // relational_expression '<' shift_expression in relational_expression (part of constant_expression).
    | TOKEN_INT8_CONSTANT {
        $$ = new TemplateArg(new ConstExpr(AtomicType::UniformInt8->GetAsConstType(),
                           (int8_t)yylval.intVal, @1), @1);
    }
    | TOKEN_UINT8_CONSTANT {
        $$ = new TemplateArg(new ConstExpr(AtomicType::UniformUInt8->GetAsConstType(),
                           (uint8_t)yylval.intVal, @1), @1);
    }
    | TOKEN_INT16_CONSTANT {
        $$ = new TemplateArg(new ConstExpr(AtomicType::UniformInt16->GetAsConstType(),
                           (int16_t)yylval.intVal, @1), @1);
    }
    | TOKEN_UINT16_CONSTANT {
        $$ = new TemplateArg(new ConstExpr(AtomicType::UniformUInt16->GetAsConstType(),
                           (uint16_t)yylval.intVal, @1), @1);
    }
    | TOKEN_INT32_CONSTANT {
        $$ = new TemplateArg(new ConstExpr(AtomicType::UniformInt32->GetAsConstType(),
                           (int32_t)yylval.intVal, @1), @1);
    }
    | TOKEN_UINT32_CONSTANT {
        $$ = new TemplateArg(new ConstExpr(AtomicType::UniformUInt32->GetAsConstType(),
                           (uint32_t)yylval.intVal, @1), @1);
    }
    | TOKEN_INT64_CONSTANT {
        $$ = new TemplateArg(new ConstExpr(AtomicType::UniformInt64->GetAsConstType(),
                           (int64_t)yylval.intVal, @1), @1);
    }
    | TOKEN_UINT64_CONSTANT {
        $$ = new TemplateArg(new ConstExpr(AtomicType::UniformUInt64->GetAsConstType(),
                           (uint64_t)yylval.intVal, @1), @1);
    }
    | TOKEN_TRUE {
        $$ = new TemplateArg(new ConstExpr(AtomicType::UniformBool->GetAsConstType(), true, @1), @1);
    }
    | TOKEN_FALSE {
        $$ = new TemplateArg(new ConstExpr(AtomicType::UniformBool->GetAsConstType(), false, @1), @1);
    }
    // Enums and nested templates case:
    | TOKEN_IDENTIFIER
    {
        const char *name = $1->c_str();
        Symbol *s = m->symbolTable->LookupVariable(name);
        if (s) {
            if (s->GetSymbolKind() == Symbol::SymbolKind::Enumerator ||
                s->GetSymbolKind() == Symbol::SymbolKind::TemplateNonTypeParm) {
                $$ = new TemplateArg(new SymbolExpr(s, @1), @1);
            } else {
                Error(@1, "Only integral, enum types and non-type template parameters are allowed as template arguments.");
                $$ = nullptr;
            }
        } else {
            Error(@1, "Unknown identifier");
            $$ = nullptr;
        }
        lCleanUpString($1);
    }
    ;

template_argument_list
    : template_argument
      {
          TemplateArgs *templArgs = new TemplateArgs();
          if ($1 != nullptr) {
            templArgs->push_back(*$1);
          }
          $$ = templArgs;

      }
    | template_argument_list ',' template_argument
      {
          TemplateArgs *templArgs = (TemplateArgs *) $1;
          if ($3 != nullptr) {
            templArgs->push_back(*$3);
          }
          $$ = templArgs;
      }
    ;

template_identifier
    : TOKEN_TEMPLATE_NAME
    {
        $$ = strdup(yytext);
        lCleanUpString($1);
    }
    ;

simple_template_id
    : template_identifier '<' template_argument_list '>'
      {
          // Template ID declartor
          Declarator *d = new Declarator(DK_BASE, @1);
          d->name = $1;
          // allocated by strdup in template_identifier
          free((char*)$1);
          // Arguments vector
          TemplateArgs *templArgs = (TemplateArgs *) $3;
          // Bundle template ID declarator and type list.
          $$ = new std::pair(d, templArgs);
      }
    | template_identifier
      {
          // Template ID declartor
          Declarator *d = new Declarator(DK_BASE, @1);
          d->name = $1;
          // allocated by strdup in template_identifier
          free((char*)$1);
          // Arguments vector
          TemplateArgs *templArgs = new TemplateArgs();
          // Bundle template ID declarator and empty type list.
          $$ = new std::pair(d, templArgs);
      }

    ;

 // template int foo<int>(int);
template_function_instantiation
    : TOKEN_TEMPLATE declaration_specifiers simple_template_id '(' parameter_type_list ')' ';'
      {
          SimpleTemplateIDType *simpleTemplID = (SimpleTemplateIDType *) $3;

          // Function declarator
          Declarator *d = new Declarator(DK_FUNCTION, Union(@1, @6));
          d->child = simpleTemplID->first;
          if ($5 != nullptr) {
              d->functionParams = *$5;
              // parameter_type_list returns vector of Declarations that is not needed anymore.
              delete $5;
          }

          d->InitFromDeclSpecs($2);
          lCheckTemplateDeclSpecs($2, d->pos, TemplateType::Instantiation, $3->first->name.c_str());
          const FunctionType *ftype = CastType<FunctionType>(d->type);
          bool isInline = ($2->typeQualifiers & TYPEQUAL_INLINE);
          bool isNoInline = ($2->typeQualifiers & TYPEQUAL_NOINLINE);
          if ($3->second->size() == 0) {
              Error(d->pos, "Template arguments deduction is not yet supported in explicit template instantiation.");
          }
          m->AddFunctionTemplateInstantiation($3->first->name, *$3->second, ftype, $2->storageClass, isInline, isNoInline, Union(@1, @6));

          // deallocate SimpleTemplateIDType returned by simple_template_id
          lFreeSimpleTemplateID(simpleTemplID);
      }
    | TOKEN_TEMPLATE declaration_specifiers simple_template_id '(' ')' ';'
      {
          SimpleTemplateIDType *simpleTemplID = (SimpleTemplateIDType *) $3;

          // Function declarator
          Declarator *d = new Declarator(DK_FUNCTION, Union(@1, @5));
          d->child = simpleTemplID->first;

          d->InitFromDeclSpecs($2);
          lCheckTemplateDeclSpecs($2, d->pos, TemplateType::Instantiation, $3->first->name.c_str());
          const FunctionType *ftype = CastType<FunctionType>(d->type);
          bool isInline = ($2->typeQualifiers & TYPEQUAL_INLINE);
          bool isNoInline = ($2->typeQualifiers & TYPEQUAL_NOINLINE);
          if ($3->second->size() == 0) {
              Error(d->pos, "Template arguments deduction is not yet supported in explicit template instantiation.");
          }
          m->AddFunctionTemplateInstantiation($3->first->name, *$3->second, ftype, $2->storageClass, isInline, isNoInline, Union(@1, @5));

          // deallocate SimpleTemplateIDType returned by simple_template_id
          lFreeSimpleTemplateID(simpleTemplID);
      }
    | TOKEN_TEMPLATE declaration_specifiers simple_template_id '(' error ')' ';'
      {
          // deallocate SimpleTemplateIDType returned by simple_template_id
          lFreeSimpleTemplateID($3);
      }
    ;

// Template specialization, a-la
// template <> int foo<int>(int) { ... }
template_function_specialization_declaration
    : TOKEN_TEMPLATE '<' '>' declaration_specifiers simple_template_id '(' parameter_type_list ')'
      {
        // Function declarator
        Declarator *d = new Declarator(DK_FUNCTION, Union(@1, @8));
        d->child = $5->first;

        if ($7 != nullptr) {
            d->functionParams = *$7;
            // parameter_type_list returns vector of Declarations that is not needed anymore.
            delete $7;
        }
        TemplateArgs *templArgs = new TemplateArgs(*$5->second);
        Assert(templArgs);
        lAddTemplateSpecialization(*templArgs, $4, d);
        m->symbolTable->PushScope();

        lAddFunctionParams(d);
        lAddMaskToSymbolTable(@5);
        // deallocate SimpleTemplateIDType returned by simple_template_id
        lFreeSimpleTemplateID($5);
        $$ = new std::pair(d, templArgs);
      }
    | TOKEN_TEMPLATE '<' '>' declaration_specifiers simple_template_id '(' ')'
      {
        Declarator *d = new Declarator(DK_FUNCTION, Union(@1, @5));
        d->child = $5->first;
        TemplateArgs *templArgs = new TemplateArgs(*$5->second);
        Assert(templArgs);
        lAddTemplateSpecialization(*templArgs, $4, d);
        m->symbolTable->PushScope();

        lAddMaskToSymbolTable(@5);
        // deallocate SimpleTemplateIDType returned by simple_template_id
        lFreeSimpleTemplateID($5);
        $$ = new std::pair(d, templArgs);
      }
    | TOKEN_TEMPLATE '<' '>' declaration_specifiers simple_template_id '(' error ')'
      {
        m->symbolTable->PushScope();
        // deallocate SimpleTemplateIDType returned by simple_template_id
        lFreeSimpleTemplateID($5);
        $$ = nullptr;
      }
    ;

template_function_specialization
    : template_function_specialization_declaration ';'
      {
          if ($1 != nullptr) {
            // deallocate TemplateSymbol created in template_declaration
            lFreeSimpleTemplateID($1);
          }
          // End templates parameters definition scope
          m->symbolTable->PopScope();
      }
    | template_function_specialization_declaration compound_statement
      {
        if ($1 != nullptr) {
            Declarator *d = $1->first;
            const FunctionType *ftype = CastType<FunctionType>(d->type);
            if (ftype == nullptr)
                AssertPos(@1, m->errorCount > 0);
            else {
                Stmt *code = $2;
                if (code == nullptr) code = new StmtList(@2);
                m->AddFunctionTemplateSpecializationDefinition(d->name, ftype, *$1->second, Union(@1, @2), code);
            }
           lFreeSimpleTemplateID($1);
        }
        m->symbolTable->PopScope();
      }
    ;

%%


void yyerror(const char *s) {
    if (strlen(yytext) == 0)
        Error(yylloc, "Premature end of file: %s.", s);
    else
        Error(yylloc, "%s.", s);
}

void lCleanUpString(std::string *s) {
    if (s) {
         delete s;
    }
}

void lFreeSimpleTemplateID(void *p) {
    SimpleTemplateIDType *sid = (SimpleTemplateIDType*) p;
    TemplateArgs *templArgs = sid->second;
    if (templArgs) {
        delete templArgs;
    }
    if (sid) {
        delete sid;
    }
}

static int
lYYTNameErr (char *yyres, const char *yystr)
{
  extern std::map<std::string, std::string> tokenNameRemap;
  Assert(tokenNameRemap.size() > 0);
  if (tokenNameRemap.find(yystr) != tokenNameRemap.end()) {
      std::string n = tokenNameRemap[yystr];
      if (yyres == nullptr)
          return n.size();
      else
          return yystpcpy(yyres, n.c_str()) - yyres;
  }

  if (*yystr == '"')
    {
      YYSIZE_T yyn = 0;
      char const *yyp = yystr;

      for (;;)
        switch (*++yyp)
          {
          case '\'':
          case ',':
            goto do_not_strip_quotes;

          case '\\':
            if (*++yyp != '\\')
              goto do_not_strip_quotes;
            /* Fall through.  */
          default:
            if (yyres)
              yyres[yyn] = *yyp;
            yyn++;
            break;

          case '"':
            if (yyres)
              yyres[yyn] = '\0';
            return yyn;
          }
    do_not_strip_quotes: ;
    }

  if (! yyres)
    return yystrlen (yystr);

  return yystpcpy (yyres, yystr) - yyres;
}

static void
lSuggestBuiltinAlternates() {
    std::vector<std::string> builtinTokens;
    const char **token = lBuiltinTokens;
    while (*token) {
        builtinTokens.push_back(*token);
        ++token;
    }
    std::vector<std::string> alternates = MatchStrings(yytext, builtinTokens);
    std::string alts = lGetAlternates(alternates);
    if (alts.size() > 0)
         Error(yylloc, "%s", alts.c_str());
}


static void
lSuggestParamListAlternates() {
    std::vector<std::string> builtinTokens;
    const char **token = lParamListTokens;
    while (*token) {
        builtinTokens.push_back(*token);
        ++token;
    }
    std::vector<std::string> alternates = MatchStrings(yytext, builtinTokens);
    std::string alts = lGetAlternates(alternates);
    if (alts.size() > 0)
        Error(yylloc, "%s", alts.c_str());
}


static void
lAddDeclaration(DeclSpecs *ds, Declarator *decl) {
    if (ds == nullptr || decl == nullptr)
        // Error happened earlier during parsing
        return;

    decl->InitFromDeclSpecs(ds);
    if (ds->storageClass.IsTypedef()) {
        const StructType *st = CastType<StructType>(decl->type);
        if (st && st->IsAnonymousType()) {
            st = st->GetAsNamed(decl->name);
            m->AddTypeDef(decl->name, st, decl->pos);
        } else {
            AttributeList *attrs = decl->attributeList;
            if (attrs && attrs->HasAttribute("aligned")) {
                unsigned int alignment = attrs->GetAlignedAttrValue(decl->pos);
                const Type *aligned_type = decl->type->GetAsAlignedType(alignment);
                m->AddTypeDef(decl->name, aligned_type, decl->pos);
            } else {
                m->AddTypeDef(decl->name, decl->type, decl->pos);
            }
        }
    } else {
        if (decl->type == nullptr) {
            Assert(m->errorCount > 0);
            return;
        }

        decl->type = decl->type->ResolveUnboundVariability(Variability::Varying);

        const FunctionType *ft = CastType<FunctionType>(decl->type);
        if (ft != nullptr) {
            bool isInline = (ds->typeQualifiers & TYPEQUAL_INLINE);
            bool isNoInline = (ds->typeQualifiers & TYPEQUAL_NOINLINE);
            bool isVectorCall = (ds->typeQualifiers & TYPEQUAL_VECTORCALL);
            bool isRegCall = (ds->typeQualifiers & TYPEQUAL_REGCALL);
            Declarator *funcDecl = decl;

            // In a case when function return type is a pointer we have decl
            // with DK_POINTER kind. We need to traverse childs to get
            // DK_FUNCTION declarator.
            while (funcDecl && funcDecl->kind != DK_FUNCTION) {
                funcDecl = funcDecl->child;
            }
            Assert(funcDecl);

            // For pointer return type we have attributeList attached to decl
            // (DK_POINTER) whereas we need to pass DK_FUNCTION declarator to
            // AddFunctionDeclaration. Create a copy of DK_FUNCTION and attach
            // attributeList from DK_POINTER.
            Declarator funcDeclCopy = *funcDecl;
            if (decl->attributeList) {
                funcDeclCopy.attributeList = new AttributeList(*decl->attributeList);
            }

            m->AddFunctionDeclaration(decl->name, ft, ds->storageClass, &funcDeclCopy,
                                      isInline, isNoInline, isVectorCall, isRegCall, decl->pos);
        }
        else {
            bool isConst = (ds->typeQualifiers & TYPEQUAL_CONST) != 0;
            m->AddGlobalVariable(decl, isConst);
        }
    }
}

static void
lCheckTemplateDeclSpecs(DeclSpecs *ds, SourcePos pos, TemplateType type, const char* name) {
    std::string templateTypeStr;
    switch (type) {
        case TemplateType::Template:
            templateTypeStr = "function template";
            break;
        case TemplateType::Instantiation:
            templateTypeStr = "template instantiation";
            break;
        case TemplateType::Specialization:
            templateTypeStr = "template specialization";
            break;
        default:
            FATAL("Unhandled template type in lCheckTemplateDeclSpecs");
    }
    if (ds->typeQualifiers & TYPEQUAL_TASK){
        Error(pos, "'task' not supported for %s.", templateTypeStr.c_str());
        return;
    }
    if (ds->typeQualifiers & TYPEQUAL_EXPORT) {
        Error(pos, "'export' not supported for %s.", templateTypeStr.c_str());
        return;
    }
    if (ds->storageClass == StorageClass::TYPEDEF) {
        Error(pos, "Illegal \"typedef\" provided with %s.", templateTypeStr.c_str());
        return;
    }
    // We can't support extern "C"/extern "SYCL" for templates because
    // we need mangling information.
    if (ds->storageClass.IsExternC() || ds->storageClass.IsExternSYCL()) {
        Error(pos, "Illegal linkage provided with %s.", templateTypeStr.c_str());
        return;
    }
    Assert(ds->storageClass.IsNone() || ds->storageClass.IsStatic() || ds->storageClass.IsExtern());
    bool isVectorCall = (ds->typeQualifiers & TYPEQUAL_VECTORCALL);
    if (isVectorCall) {
        Error(pos, "Illegal to use \"__vectorcall\" qualifier on non-extern function \"%s\".", name);
    }
    bool isRegCall = (ds->typeQualifiers & TYPEQUAL_REGCALL);
    if (isRegCall) {
        Error(pos, "Illegal to use \"__regcall\" qualifier on non-extern function \"%s\".", name);
    }
}

static void
lAddTemplateDeclaration(TemplateParms *templateParmList, DeclSpecs *ds, Declarator *decl) {
    if (ds == nullptr || decl == nullptr) {
        // Error happened earlier during parsing
        return;
    }

    decl->InitFromDeclSpecs(ds);
    lCheckTemplateDeclSpecs(ds, decl->pos, TemplateType::Template, decl->name.c_str());

    if (decl->type == nullptr) {
        Assert(m->errorCount > 0);
        return;
    }

    const FunctionType *ft = CastType<FunctionType>(decl->type);
    if (ft != nullptr) {
        bool isInline = (ds->typeQualifiers & TYPEQUAL_INLINE);
        bool isNoInline = (ds->typeQualifiers & TYPEQUAL_NOINLINE);
        m->AddFunctionTemplateDeclaration(templateParmList, decl->name, ft, ds->storageClass,
                                          isInline, isNoInline, decl->pos);
    }
    else {
        Error(decl->pos, "Only function templates are supported.");
    }

}

static void
lAddTemplateSpecialization(const TemplateArgs &templArgs, DeclSpecs *ds, Declarator *decl) {
    if (ds == nullptr || decl == nullptr)
        // Error happened earlier during parsing
        return;

    decl->InitFromDeclSpecs(ds);
    lCheckTemplateDeclSpecs(ds, decl->pos, TemplateType::Specialization, decl->name.c_str());

    if (decl->type == nullptr) {
        Assert(m->errorCount > 0);
        return;
    }

    if (templArgs.size() == 0) {
        Error(decl->pos, "Template arguments deduction is not yet supported in template function specialization.");
        return;
    }

    const FunctionType *ftype = CastType<FunctionType>(decl->type);
    if (ftype != nullptr) {
        bool isInline = (ds->typeQualifiers & TYPEQUAL_INLINE);
        bool isNoInline = (ds->typeQualifiers & TYPEQUAL_NOINLINE);
        m->AddFunctionTemplateSpecializationDeclaration(decl->name, ftype, templArgs, ds->storageClass,
                                                        isInline, isNoInline, decl->pos);
    }
    else {
        Error(decl->pos, "Only function template specializations are supported.");
    }
}

/** We're about to start parsing the body of a function; add all of the
    parameters to the symbol table so that they're available.
*/
static void
lAddFunctionParams(Declarator *decl) {
    // It's responsibility of the caller to create a new symbol table scope.
    // For regular functions, the scope starts before function parameters.
    // For template functions, the scope starts before template parameters.
    if (decl == nullptr) {
        return;
    }

    // walk down to the declarator for the function itself
    while (decl->kind != DK_FUNCTION && decl->child != nullptr)
        decl = decl->child;
    if (decl->kind != DK_FUNCTION) {
        AssertPos(decl->pos, m->errorCount > 0);
        return;
    }

    // now loop over its parameters and add them to the symbol table
    for (unsigned int i = 0; i < decl->functionParams.size(); ++i) {
        Declaration *pdecl = decl->functionParams[i];
        Assert(pdecl != nullptr && pdecl->declarators.size() == 1);
        Declarator *declarator = pdecl->declarators[0];
        if (declarator == nullptr)
            AssertPos(decl->pos, m->errorCount > 0);
        else {
            Symbol *sym = new Symbol(declarator->name, declarator->pos, Symbol::SymbolKind::FunctionParm,
                                     declarator->type, declarator->storageClass);

            AttributeList *AL = declarator->attributeList;
            if (AL) {
                // Check for unknown attributes for parameters in function definitions.
                AL->CheckForUnknownAttributes(declarator->pos);
            }
#ifndef NDEBUG
            bool ok = m->symbolTable->AddVariable(sym);
            if (ok == false)
                AssertPos(decl->pos, m->errorCount > 0);
#else
            m->symbolTable->AddVariable(sym);
#endif
        }
    }

    // The corresponding pop scope happens in function_definition rules
    // above...
}


/** Add a symbol for the built-in mask variable to the symbol table */
static void lAddMaskToSymbolTable(SourcePos pos) {
    const Type *t = nullptr;
    switch (g->target->getMaskBitCount()) {
    case 1:
        t = AtomicType::VaryingBool;
        break;
    case 8:
        t = AtomicType::VaryingUInt8;
        break;
    case 16:
        t = AtomicType::VaryingUInt16;
        break;
    case 32:
        t = AtomicType::VaryingUInt32;
        break;
    case 64:
        t = AtomicType::VaryingUInt64;
        break;
    default:
        FATAL("Unhandled mask bitsize in lAddMaskToSymbolTable");
    }

    t = t->GetAsConstType();
    Symbol *maskSymbol = new Symbol("__mask", pos, Symbol::SymbolKind::Default, t);
    m->symbolTable->AddVariable(maskSymbol);
}


/** Add the thread index and thread count variables to the symbol table
    (this should only be done for 'task'-qualified functions. */
static void lAddThreadIndexCountToSymbolTable(SourcePos pos) {
    const Type *type = AtomicType::UniformUInt32->GetAsConstType();

    Symbol *threadIndexSym = new Symbol("threadIndex", pos, Symbol::SymbolKind::Default, type);
    m->symbolTable->AddVariable(threadIndexSym);

    Symbol *threadCountSym = new Symbol("threadCount", pos, Symbol::SymbolKind::Default, type);
    m->symbolTable->AddVariable(threadCountSym);

    Symbol *taskIndexSym = new Symbol("taskIndex", pos, Symbol::SymbolKind::Default, type);
    m->symbolTable->AddVariable(taskIndexSym);

    Symbol *taskCountSym = new Symbol("taskCount", pos, Symbol::SymbolKind::Default, type);
    m->symbolTable->AddVariable(taskCountSym);

    Symbol *taskIndexSym0 = new Symbol("taskIndex0", pos, Symbol::SymbolKind::Default, type);
    m->symbolTable->AddVariable(taskIndexSym0);
    Symbol *taskIndexSym1 = new Symbol("taskIndex1", pos, Symbol::SymbolKind::Default, type);
    m->symbolTable->AddVariable(taskIndexSym1);
    Symbol *taskIndexSym2 = new Symbol("taskIndex2", pos, Symbol::SymbolKind::Default, type);
    m->symbolTable->AddVariable(taskIndexSym2);


    Symbol *taskCountSym0 = new Symbol("taskCount0", pos, Symbol::SymbolKind::Default, type);
    m->symbolTable->AddVariable(taskCountSym0);
    Symbol *taskCountSym1 = new Symbol("taskCount1", pos, Symbol::SymbolKind::Default, type);
    m->symbolTable->AddVariable(taskCountSym1);
    Symbol *taskCountSym2 = new Symbol("taskCount2", pos, Symbol::SymbolKind::Default, type);
    m->symbolTable->AddVariable(taskCountSym2);
}


/** Small utility routine to construct a string for error messages that
    suggests alternate tokens for possibly-misspelled ones... */
static std::string lGetAlternates(std::vector<std::string> &alternates) {
    std::string alts;
    if (alternates.size()) {
        alts += " Did you mean ";
        for (unsigned int i = 0; i < alternates.size(); ++i) {
            alts += std::string("\"") + alternates[i] + std::string("\"");
            if (i < alternates.size() - 1) alts += ", or ";
        }
        alts += "?";
    }
    return alts;
}


/** Given an expression, see if it is equal to a compile-time constant
    integer value.  If so, return true and return the value in *value.
    If the expression isn't a compile-time constant or isn't an integer
    type, return false.
*/
static bool
lGetConstantIntOrSymbol(Expr *expr, std::variant<std::monostate, int, Symbol*> *value, SourcePos pos, const char *usage) {
    if (expr == nullptr)
        return false;
    expr = TypeCheckAndOptimize(expr);
    if (expr == nullptr)
        return false;
    const SymbolExpr* se= llvm::dyn_cast<SymbolExpr>(expr);
    if (se) {
        Symbol* s = se->GetBaseSymbol();
        if (s->GetSymbolKind() == Symbol::SymbolKind::TemplateNonTypeParm) {
            value->emplace<Symbol*>(s);
            return true;
        }
    }
    std::pair<llvm::Constant *, bool> cValPair = expr->GetConstant(expr->GetType());
    llvm::Constant *cval = cValPair.first;
    if (cval == nullptr) {
        Error(pos, "%s must be a compile-time constant or template non-type parameter.", usage);
        return false;
    }
    else {
        llvm::ConstantInt *ci = llvm::dyn_cast<llvm::ConstantInt>(cval);
        if (ci == nullptr) {
            Error(pos, "%s must be a compile-time integer constant.", usage);
            return false;
        }
        if ((int64_t)((int32_t)ci->getSExtValue()) != ci->getSExtValue()) {
            Error(pos, "%s must be representable with a 32-bit integer.", usage);
            return false;
        }
        const Type *type = expr->GetType();
        int resultValue = type->IsUnsignedType() ? (int)ci->getZExtValue() : (int)ci->getSExtValue();
        value->emplace<int>(resultValue);
        return true;
    }
}


static EnumType *
lCreateEnumType(const char *name, std::vector<Symbol *> *enums, SourcePos pos) {
    if (enums == nullptr)
        return nullptr;

    EnumType *enumType = name ? new EnumType(name, pos) : new EnumType(pos);
    if (name != nullptr)
        m->symbolTable->AddType(name, enumType, pos);

    lFinalizeEnumeratorSymbols(*enums, enumType);
    for (unsigned int i = 0; i < enums->size(); ++i)
        m->symbolTable->AddVariable((*enums)[i]);
    enumType->SetEnumerators(*enums);
    return enumType;
}

/* Create StructType and add it to the symbol table if it's not anonymous */
static StructType *lCreateStructType(const std::string &name, const std::vector<StructDeclaration *> *decl,
                                     AttributeList *attrs, SourcePos pos) {
    llvm::SmallVector<const Type *, 8> elementTypes;
    llvm::SmallVector<std::string, 8> elementNames;
    llvm::SmallVector<SourcePos, 8> elementPositions;
    unsigned int alignment = 0;

    if (attrs) {
        alignment = attrs->GetAlignedAttrValue(pos);
    }

    if (decl) {
        GetStructTypesNamesPositions(*decl, &elementTypes, &elementNames, &elementPositions);
    }

    StructType *st =
        new StructType(name, elementTypes, elementNames, elementPositions, false, Variability::Unbound, pos, alignment);
    st->RegisterInStructTypeMap();

    if (!name.empty()) {
        m->symbolTable->AddType(name.c_str(), st, pos);
    }

    return st;
}

/** Given an array of enumerator symbols, make sure each of them has a
    ConstExpr * in their Symbol::constValue member that stores their
    unsigned integer value.  Symbols that had values explicitly provided
    in the source file will already have ConstExpr * set; we just need
    to set the values for the others here.
*/
static void
lFinalizeEnumeratorSymbols(std::vector<Symbol *> &enums,
                           const EnumType *enumType) {
    const Type *constUniformType = enumType->GetAsConstType()->GetAsUniformType();

    /* nextVal tracks the value for the next enumerant.  It starts from
       zero and goes up with each successive enumerant.  If any of them
       has a value specified, then nextVal is ignored for that one and is
       set to one plus that one's value for the default value for the next
       one. */
    uint32_t nextVal = 0;

    for (unsigned int i = 0; i < enums.size(); ++i) {
        enums[i]->type = constUniformType;
        if (enums[i]->constValue != nullptr) {
            /* Already has a value, so first update nextVal with it. */
            int count = enums[i]->constValue->GetValues(&nextVal);
            AssertPos(enums[i]->pos, count == 1);
            ++nextVal;

            /* When the source file as being parsed, the ConstExpr for any
               enumerant with a specified value was set to have unsigned
               int32 type, since we haven't created the parent EnumType
               by then.  Therefore, add a little type cast from uint32 to
               the actual enum type here and optimize it, which will have
               us end up with a ConstExpr with the desired EnumType... */
            Expr *castExpr = new TypeCastExpr(constUniformType, enums[i]->constValue,
                                              enums[i]->pos);
            castExpr = TypeCheckAndOptimize(castExpr);
            if (castExpr == nullptr) {
                AssertPos(enums[i]->pos, m->errorCount > 0);
                continue;
            }
            enums[i]->constValue = llvm::dyn_cast<ConstExpr>(castExpr);
            AssertPos(enums[i]->pos, enums[i]->constValue != nullptr);
        }
        else {
            enums[i]->constValue = new ConstExpr(constUniformType, nextVal++,
                                                 enums[i]->pos);
        }
    }
}
